diff --git a/src/java/com/threerings/cast/ActionSequence.java b/src/java/com/threerings/cast/ActionSequence.java index 95cf208a2..9faee72f8 100644 --- a/src/java/com/threerings/cast/ActionSequence.java +++ b/src/java/com/threerings/cast/ActionSequence.java @@ -1,37 +1,25 @@ // -// $Id: ActionSequence.java,v 1.1 2001/11/01 01:40:42 shaper Exp $ +// $Id: ActionSequence.java,v 1.2 2001/11/27 08:09:34 mdb Exp $ package com.threerings.cast; import java.awt.Point; - -import com.threerings.media.tile.TileSet; +import java.io.Serializable; /** - * The action sequence class describes a single character animation - * sequence. An animation sequence may consist of multiple frames of - * animation, renders at a particular frame rate, and has an origin - * point that specifies where the base of the character in the - * animation sequence is to be placed. + * 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 +public class ActionSequence implements Serializable { - /** The unique action sequence identifier. */ - public int asid; - /** The action sequence name. */ public String name; - /** The file id specifier for the tile set image file name. */ - public String fileid; - - /** The tile set description for this sequence. Intended for - * cloning with an image path to reference an actual set of tile - * images suiting the action sequence. */ - public TileSet tileset; - /** The number of frames per second to show when animating. */ - public int fps; + public int framesPerSecond; /** The position of the character's base for this sequence. */ public Point origin = new Point(); @@ -41,7 +29,7 @@ public class ActionSequence */ public String toString () { - return "[asid=" + asid + ", name=" + name + - ", fps=" + fps + ", origin=" + origin + "]"; + return "[name=" + name + ", framesPerSecond=" + framesPerSecond + + ", origin=" + origin + "]"; } } diff --git a/src/java/com/threerings/cast/CharacterComponent.java b/src/java/com/threerings/cast/CharacterComponent.java index 1a2142fe3..c2b02dad0 100644 --- a/src/java/com/threerings/cast/CharacterComponent.java +++ b/src/java/com/threerings/cast/CharacterComponent.java @@ -1,8 +1,10 @@ // -// $Id: CharacterComponent.java,v 1.3 2001/11/01 01:40:42 shaper Exp $ +// $Id: CharacterComponent.java,v 1.4 2001/11/27 08:09:34 mdb Exp $ package com.threerings.cast; +import java.io.Serializable; + import com.samskivert.util.StringUtil; import com.threerings.media.sprite.MultiFrameImage; @@ -10,71 +12,43 @@ import com.threerings.media.sprite.Sprite; /** * The character component represents a single component that can be - * composited with other character components to comprise an image - * representing a single monolithic character displayable in any of - * the eight cardinal compass directions as detailed in the {@link - * com.threerings.media.sprite.Sprite} class's direction constants. + * composited with other character components to generate an image + * representing a complete character displayable in any of the eight + * compass directions as detailed in the {@link Sprite} class direction + * constants. */ -public class CharacterComponent +public class CharacterComponent implements Serializable { + /** The unique component identifier. */ + public int componentId; + + /** The component's name. */ + public String name; + + /** The class of components to which this one belongs. */ + public ComponentClass componentClass; + /** - * Constructs a character component. + * Constructs a character component with the specified id of the + * specified class. */ - public CharacterComponent ( - int cid, String fileid, ActionSequence seqs[], ComponentClass cclass) + public CharacterComponent (int componentId, String name, + ComponentClass compClass, FrameProvider fprov) { - _cid = cid; - _fileid = fileid; - _seqs = seqs; - _cclass = cclass; + this.componentId = componentId; + this.name = name; + this.componentClass = compClass; + _frameProvider = fprov; } /** - * Returns the unique component identifier. + * Returns the image frames for the specified action animation or null + * if no animation for the specified action is available for this + * component. */ - public int getId () + public MultiFrameImage[] getFrames (String action) { - return _cid; - } - - /** - * Returns the action sequences for this component. - */ - public ActionSequence[] getActionSequences () - { - return _seqs; - } - - /** - * Returns the display frames used to display this component. - */ - public MultiFrameImage[][] getFrames () - { - return _frames; - } - - /** - * Returns the file id. - */ - public String getFileId () - { - return _fileid; - } - - /** - * Returns the component class associated with this component. - */ - public ComponentClass getComponentClass () - { - return _cclass; - } - - /** - * Sets the frames used to render this component. - */ - public void setFrames (MultiFrameImage frames[][]) - { - _frames = frames; + return _frameProvider.getFrames(this, action); } /** @@ -82,22 +56,10 @@ public class CharacterComponent */ public String toString () { - return "[cid=" + _cid + ", clid=" + _cclass.clid + - ", seqs=" + StringUtil.toString(_seqs) + "]"; + return "[componentId=" + componentId + ", name=" + name + + ", class=" + componentClass + "]"; } - /** The unique character component identifier. */ - protected int _cid; - - /** The file id specifier for the tile set image file name. */ - protected String _fileid; - - /** The animation frames for each action sequence and orientation. */ - protected MultiFrameImage _frames[][]; - - /** The component class. */ - protected ComponentClass _cclass; - - /** The character action sequences. */ - protected ActionSequence _seqs[]; + /** The entity from which we obtain our animation frames. */ + protected FrameProvider _frameProvider; } diff --git a/src/java/com/threerings/cast/CharacterDescriptor.java b/src/java/com/threerings/cast/CharacterDescriptor.java index 2d18a20d2..de3ab0d1f 100644 --- a/src/java/com/threerings/cast/CharacterDescriptor.java +++ b/src/java/com/threerings/cast/CharacterDescriptor.java @@ -1,8 +1,9 @@ // -// $Id: CharacterDescriptor.java,v 1.3 2001/11/01 01:40:42 shaper Exp $ +// $Id: CharacterDescriptor.java,v 1.4 2001/11/27 08:09:34 mdb Exp $ package com.threerings.cast; +import java.util.Arrays; import com.samskivert.util.StringUtil; /** @@ -14,7 +15,7 @@ public class CharacterDescriptor /** * Constructs the character descriptor. */ - public CharacterDescriptor (int components[]) + public CharacterDescriptor (int[] components) { _components = components; } @@ -23,19 +24,42 @@ public class CharacterDescriptor * Returns an array of the component identifiers comprising the * character described by this descriptor. */ - public int[] getComponents () + public int[] getComponentIds () { return _components; } + /** + * Compute a sensible hashcode for this object. + */ + public int hashCode () + { + int code = 0, clength = _components.length; + for (int i = 0; i < clength; i++) { + code ^= _components[i]; + } + return code; + } + + /** + * Compares this character descriptor to another. + */ + public boolean equals (Object other) + { + if (other instanceof CharacterDescriptor) { + return Arrays.equals(_components, + ((CharacterDescriptor)other)._components); + } else { + return false; + } + } + /** * Returns a string representation of this character descriptor. */ public String toString () { - StringBuffer buf = new StringBuffer(); - buf.append("[").append(StringUtil.toString(_components)); - return buf.append("]").toString(); + return "[cids=" + StringUtil.toString(_components) + "]"; } /** The component identifiers comprising the character. */ diff --git a/src/java/com/threerings/cast/CharacterManager.java b/src/java/com/threerings/cast/CharacterManager.java index 59fba17df..c0cfdce26 100644 --- a/src/java/com/threerings/cast/CharacterManager.java +++ b/src/java/com/threerings/cast/CharacterManager.java @@ -1,11 +1,13 @@ // -// $Id: CharacterManager.java,v 1.9 2001/11/02 15:28:20 shaper Exp $ +// $Id: CharacterManager.java,v 1.10 2001/11/27 08:09:34 mdb Exp $ package com.threerings.cast; -import java.util.*; +import java.util.Arrays; +import java.util.Iterator; +import java.util.HashMap; -import com.samskivert.util.CollectionUtil; +import com.samskivert.util.Tuple; import com.threerings.media.sprite.MultiFrameImage; import com.threerings.media.sprite.Sprite; @@ -14,21 +16,47 @@ import com.threerings.cast.Log; import com.threerings.cast.util.TileUtil; /** - * The character manager provides facilities for constructing sprites - * that are used to represent characters in a scene. + * 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 { /** * Constructs the character manager. */ - public CharacterManager (ComponentRepository repo) + public CharacterManager (ComponentRepository crepo) { // keep this around - _repo = repo; + _crepo = crepo; - // determine component class render order - _renderRank = getRenderRank(); + // populate our actions table + Iterator iter = crepo.enumerateActionSequences(); + while (iter.hasNext()) { + ActionSequence action = (ActionSequence)iter.next(); + _actions.put(action.name, action); + } + } + + /** + * Instructs the character manager to construct instances of this + * derived class of {@link CharacterSprite} when creating new sprites. + * + * @exception IllegalArgumentException thrown if the supplied class + * does not derive from {@link CharacterSprite}. + */ + public void setCharacterClass (Class charClass) + { + // sanity check + if (!CharacterSprite.class.isAssignableFrom(charClass)) { + String errmsg = "Requested to use character sprite class that " + + "does not derive from CharacterSprite " + + "[class=" + charClass.getName() + "]."; + throw new IllegalArgumentException(errmsg); + } + + // make a note of it + _charClass = charClass; } /** @@ -40,127 +68,12 @@ public class CharacterManager */ public CharacterSprite getCharacter (CharacterDescriptor desc) { - long start = System.currentTimeMillis(); - - // get the array of component ids of each class - CharacterComponent components[] = getComponents(desc.getComponents()); - if (components == null || components.length == 0) { - Log.warning("No character components in descriptor."); - return null; - } - // assume all components support the same set of action sequences - ActionSequence seqs[] = components[0].getActionSequences(); - - // create the composite character image - MultiFrameImage frames[][] = - createCompositeFrames(seqs.length, components); - if (frames == null) { - return null; - } - - // instantiate the character sprite - CharacterSprite sprite = createSprite(); - if (sprite == null) { - return null; - } - - // populate the character sprite with its attributes - sprite.setAnimations(seqs, frames); - - long end = System.currentTimeMillis(); - Log.info("Generated character sprite [ms=" + (end - start) + "]."); - - return sprite; - } - - /** - * Returns an iterator over the {@link ComponentClass} objects - * representing all available character component classes. - */ - public Iterator enumerateComponentClasses () - { - return _repo.enumerateComponentClasses(); - } - - /** - * Returns an iterator over the Integer objects - * representing all available character component identifiers for - * the given character component class identifier. - */ - public Iterator enumerateComponentsByClass (int clid) - { - return _repo.enumerateComponentsByClass(clid); - } - - /** - * Instructs the character manager to construct instances of this - * derived class of CharacterSprite. - */ - public void setCharacterClass (Class charClass) - { - // sanity check - if (!CharacterSprite.class.isAssignableFrom(charClass)) { - Log.warning("Requested to use character class that does not " + - "derive from CharacterSprite " + - "[class=" + charClass.getName() + "]."); - return; - } - - // make a note of it - _charClass = charClass; - } - - /** - * Returns an array of the character component objects specified - * in the given array of component ids. - */ - protected CharacterComponent[] getComponents (int cids[]) - { - int size = cids.length; - CharacterComponent components[] = new CharacterComponent[size]; - try { - for (int ii = 0; ii < size; ii++) { - components[ii] = _repo.getComponent(cids[ii]); - } + CharacterSprite sprite = (CharacterSprite) + _charClass.newInstance(); + sprite.init(desc, this); + return sprite; - } catch (NoSuchComponentException nsce) { - Log.warning("Exception retrieving character component " + - "[nsce=" + nsce + "]."); - return null; - } - - return components; - } - - /** - * Returns an array of multi frame images containing the fully - * composited images for the given action sequences and - * components. - */ - protected MultiFrameImage[][] createCompositeFrames ( - int seqCount, CharacterComponent components[]) - { - MultiFrameImage frames[][] = - new MultiFrameImage[seqCount][Sprite.NUM_DIRECTIONS]; - - // render all component frames one atop another - for (int ii = 0; ii < _renderRank.length; ii++) { - int clidx = _renderRank[ii].clid; - TileUtil.compositeFrames(frames, components[clidx].getFrames()); - } - - return frames; - } - - /** - * Returns a new instance of the {@link CharacterSprite}-derived - * class specified for use by this character manager. - */ - protected CharacterSprite createSprite () - { - try { - return (CharacterSprite)_charClass.newInstance(); } catch (Exception e) { Log.warning("Failed to instantiate character sprite " + "[e=" + e + "]."); @@ -170,27 +83,82 @@ public class CharacterManager } /** - * Returns an array of {@link ComponentClass} objects sorted into - * the appropriate rendering order as specified by each component - * class object. + * Returns the action sequence instance with the specified name or + * null if no such sequence exists. */ - protected ComponentClass[] getRenderRank () + protected ActionSequence getActionSequence (String action) { - ArrayList classes = new ArrayList(); - CollectionUtil.addAll(classes, _repo.enumerateComponentClasses()); - - ComponentClass rank[] = new ComponentClass[classes.size()]; - classes.toArray(rank); - Arrays.sort(rank, ComponentClass.RENDER_COMP); - - return rank; + return (ActionSequence)_actions.get(action); } - /** The order in which to render component classes. */ - protected ComponentClass _renderRank[]; + /** + * Obtains the composited animation frames for the specified action + * for a character with the specified descriptor. The resulting + * composited animation will be cached. + * + * @exception NoSuchComponentException thrown if any of the components + * in the supplied descriptor do not exist. + * @exception IllegalArgumentException thrown if any of the components + * referenced in the descriptor do not support the specified action. + */ + protected MultiFrameImage[] getActionFrames ( + CharacterDescriptor descrip, String action) + throws NoSuchComponentException + { + // the cache is keyed on both values + Tuple key = new Tuple(descrip, action); + MultiFrameImage[] frames = (MultiFrameImage[])_frames.get(key); + if (frames == null) { + // do the compositing + frames = createCompositeFrames(descrip, action); + // cache the result + _frames.put(key, frames); + } + return frames; + } + + /** + * Generates the composited animation frames for the specified action + * for a character with the specified descriptor. + * + * @exception NoSuchComponentException thrown if any of the components + * in the supplied descriptor do not exist. + * @exception IllegalArgumentException thrown if any of the components + * referenced in the descriptor do not support the specified action. + */ + protected MultiFrameImage[] createCompositeFrames ( + CharacterDescriptor descrip, String action) + throws NoSuchComponentException + { + MultiFrameImage[] frames = new MultiFrameImage[Sprite.NUM_DIRECTIONS]; + + // obtain the necessary components + int[] cids = descrip.getComponentIds(); + int ccount = cids.length; + CharacterComponent[] components = new CharacterComponent[ccount]; + for (int i = 0; i < ccount; i++) { + components[i] = _crepo.getComponent(cids[i]); + } + + // sort them into the proper rendering order + Arrays.sort(components, ComponentClass.RENDER_COMP); + + // now composite the component frames, one atop the next + for (int i = 0; i < ccount; i++) { + TileUtil.compositeFrames(frames, components[i].getFrames(action)); + } + + return frames; + } /** The component repository. */ - protected ComponentRepository _repo; + protected ComponentRepository _crepo; + + /** A table of our action sequences. */ + protected HashMap _actions = new HashMap(); + + /** A cache of composited animation frames. */ + protected HashMap _frames = new HashMap(); /** The character class to be created. */ protected Class _charClass = CharacterSprite.class; diff --git a/src/java/com/threerings/cast/CharacterSprite.java b/src/java/com/threerings/cast/CharacterSprite.java index 2be6087a5..d68eab56c 100644 --- a/src/java/com/threerings/cast/CharacterSprite.java +++ b/src/java/com/threerings/cast/CharacterSprite.java @@ -1,65 +1,70 @@ // -// $Id: CharacterSprite.java,v 1.17 2001/11/01 01:40:42 shaper Exp $ +// $Id: CharacterSprite.java,v 1.18 2001/11/27 08:09:35 mdb Exp $ package com.threerings.cast; -import java.awt.Point; - -import com.threerings.media.sprite.*; - -import com.threerings.cast.Log; +import com.threerings.media.sprite.MultiFrameImage; +import com.threerings.media.sprite.Path; +import com.threerings.media.sprite.Sprite; /** * A character sprite is a sprite that animates itself while walking * about in a scene. */ -public class CharacterSprite extends Sprite +public class CharacterSprite + extends Sprite implements StandardActions { /** - * Constructs a character sprite. + * Initializes this character sprite with the specified character + * descriptor and character manager. It will obtain animation data + * from the supplied character manager. */ - public CharacterSprite () + public void init (CharacterDescriptor descrip, CharacterManager charmgr) { + // keep track of this stuff + _descrip = descrip; + _charmgr = charmgr; + // assign an arbitrary starting orientation _orient = DIR_NORTH; } /** - * Sets the action sequences available for this character sprite - * and the animation frames that go along with each action. - * Resets the character's currently selected action sequence to - * the standing sequence. + * Sets the action sequence used when rendering the character, from + * the set of available sequences. */ - public void setAnimations (ActionSequence[] seqs, - MultiFrameImage anims[][]) + public void setActionSequence (String action) { - _seqs = seqs; - _anims = anims; - setActionSequence(WALKING); - } + // get a reference to the action sequence so that we can obtain + // our animation frames and configure our frames per second + ActionSequence actseq = _charmgr.getActionSequence(action); + if (actseq == null) { + String errmsg = "No such action '" + action + "'."; + throw new IllegalArgumentException(errmsg); + } - /** - * Sets the action sequence used when rendering the character, - * from the set of available sequences. - */ - public void setActionSequence (int seqidx) - { - // save off the action sequence index - _seqidx = seqidx; + try { + // obtain our animation frames for this action sequence + _frames = _charmgr.getActionFrames(_descrip, action); - // update the sprite render attributes - ActionSequence seq = _seqs[_seqidx]; - setFrames(_anims[_seqidx][_orient]); - setFrameRate(seq.fps); - setOrigin(seq.origin.x, seq.origin.y); + // update the sprite render attributes + setOrigin(actseq.origin.x, actseq.origin.y); + setFrameRate(actseq.framesPerSecond); + setFrames(_frames[_orient]); + + } catch (NoSuchComponentException nsce) { + Log.warning("Character sprite referneces non-existent " + + "component [sprite=" + this + ", err=" + nsce + "]."); + } } // documentation inherited public void setOrientation (int orient) { super.setOrientation(orient); + // update the sprite frames to reflect the direction - setActionSequence((_path == null) ? STANDING : WALKING); + setFrames(_frames[orient]); } /** @@ -117,26 +122,22 @@ public class CharacterSprite extends Sprite */ protected void halt () { + // disable animation + setAnimationMode(NO_ANIMATION); // come to a halt looking settled and at peace setActionSequence(STANDING); - // disable walking animation - setAnimationMode(NO_ANIMATION); } - /** The action sequence constant for standing. */ - protected static final int STANDING = 0; + /** A reference to the descriptor for the character that we're + * visualizing. */ + protected CharacterDescriptor _descrip; - /** The action sequence constant for walking. */ - protected static final int WALKING = 1; + /** A reference to the character manager that created us. */ + protected CharacterManager _charmgr; - /** The currently selected action sequence. */ - protected int _seqidx; - - /** The available action sequences. */ - protected ActionSequence _seqs[]; - - /** The animation frames for each action sequence and orientation. */ - protected MultiFrameImage _anims[][]; + /** The animation frames for the active action sequence in each + * orientation. */ + protected MultiFrameImage[] _frames; /** The origin of the sprite. */ protected int _xorigin, _yorigin; diff --git a/src/java/com/threerings/cast/ComponentClass.java b/src/java/com/threerings/cast/ComponentClass.java index 72117b56a..89c2c1b9a 100644 --- a/src/java/com/threerings/cast/ComponentClass.java +++ b/src/java/com/threerings/cast/ComponentClass.java @@ -1,42 +1,60 @@ // -// $Id: ComponentClass.java,v 1.1 2001/10/30 16:16:01 shaper Exp $ +// $Id: ComponentClass.java,v 1.2 2001/11/27 08:09:35 mdb Exp $ package com.threerings.cast; +import java.io.Serializable; import java.util.Comparator; /** - * The component class object denotes the particular class of - * components that a {@link CharacterComponent} object belongs to. - * Examples of feasible component classes might include "Hat", "Head", - * or "Feet". + * Denotes a class of components to which {@link CharacterComponent} + * objects belong. Examples include "Hat", "Head", and "Feet". A component + * class dictates a component's rendering priority so that components can + * be rendered in an order that causes them to overlap properly. */ -public class ComponentClass +public class ComponentClass implements Serializable { /** The comparator used to sort component class objects in render * priority order. */ public static final Comparator RENDER_COMP = new RenderComparator(); - /** The unique component class identifier. */ - public int clid; - /** The component class name. */ public String name; /** The render priority. */ - public int render; + public int renderPriority; + + /** + * Classes with the same name are the same. + */ + public boolean equals (Object other) + { + if (other instanceof ComponentClass) { + return name.equals(((ComponentClass)other).name); + } else { + return false; + } + } + + /** + * Hashcode is based on component class name. + */ + public int hashCode () + { + return name.hashCode(); + } /** * Returns a string representation of this component class. */ public String toString () { - return "[clid=" + clid + ", name=" + name + "]"; + return "[name=" + name + ", renderPriority=" + renderPriority + "]"; } /** - * The comparator used to sort component class objects in render - * priority order so that compositing components into a single + * The comparator used to sort {@link CharacterComponent} instances in + * render priority order so that compositing components into a single * character image can be done in the proper order. */ protected static class RenderComparator implements Comparator @@ -44,21 +62,15 @@ public class ComponentClass // documentation inherited public int compare (Object a, Object b) { - if (!(a instanceof ComponentClass) || - !(b instanceof ComponentClass)) { + if (!(a instanceof CharacterComponent) || + !(b instanceof CharacterComponent)) { return -1; } - ComponentClass ca = (ComponentClass)a; - ComponentClass cb = (ComponentClass)b; - - if (ca.render < cb.render) { - return -1; - } else if (ca.render == cb.render) { - return 0; - } else { - return 1; - } + CharacterComponent ca = (CharacterComponent)a; + CharacterComponent cb = (CharacterComponent)b; + return (ca.componentClass.renderPriority - + cb.componentClass.renderPriority); } // documentation inherited diff --git a/src/java/com/threerings/cast/ComponentIDBroker.java b/src/java/com/threerings/cast/ComponentIDBroker.java new file mode 100644 index 000000000..f575986fb --- /dev/null +++ b/src/java/com/threerings/cast/ComponentIDBroker.java @@ -0,0 +1,45 @@ +// +// $Id: ComponentIDBroker.java,v 1.1 2001/11/27 08:09:35 mdb Exp $ + +package com.threerings.cast; + +import com.samskivert.io.PersistenceException; + +/** + * Brokers component ids. The component repository interface makes + * available a collection of components based on a unique identifier. The + * expectation is that a collection of components will be used to populate + * a repository and in that population process, component ids will be + * assigned to the components. The component id broker system provides a + * means by which named components can be mapped consistently to a set of + * component ids. Humans can then be responsible for assigning unique + * names to the components and the broker will ensure that those names map + * to unique ids that won't change if the repository is rebuilt from the + * source components. + */ +public interface ComponentIDBroker +{ + /** + * Returns the unique identifier for the named component. If no + * identifier has yet been assigned to the specified named component, + * one should be assigned and returned. + * + * @param cclass the name of the class to which the component belongs. + * @param cname the name of the component. + * + * @exception PersistenceException thrown if an error occurs + * communicating with the underlying persistence mechanism used to + * store the name to id mappings. + */ + public int getComponentID (String cclass, String cname) + throws PersistenceException; + + /** + * When the user of a component id broker is done obtaining component + * ids, it must call this method to give the component id broker an + * opportunity to flush any newly created component ids back to its + * persistent store. + */ + public void commit () + throws PersistenceException; +} diff --git a/src/java/com/threerings/cast/ComponentRepository.java b/src/java/com/threerings/cast/ComponentRepository.java index ca575a285..0c90a19aa 100644 --- a/src/java/com/threerings/cast/ComponentRepository.java +++ b/src/java/com/threerings/cast/ComponentRepository.java @@ -1,34 +1,40 @@ // -// $Id: ComponentRepository.java,v 1.3 2001/11/01 01:40:42 shaper Exp $ +// $Id: ComponentRepository.java,v 1.4 2001/11/27 08:09:35 mdb Exp $ package com.threerings.cast; import java.util.Iterator; /** - * The component repository interface is intended to be implemented by - * classes that provide access to {@link CharacterComponent} objects - * keyed on their unique component identifier. + * 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 - * unique component identifier. + * component identifier. */ - public CharacterComponent getComponent (int cid) + public CharacterComponent getComponent (int componentId) throws NoSuchComponentException; /** - * Returns an iterator over the {@link ComponentClass} objects - * representing all available character component classes. + * Iterates over the {@link ComponentClass} instances representing all + * available character component classes. */ public Iterator enumerateComponentClasses (); /** - * Returns an iterator over the Integer objects - * representing all available character component identifiers for - * the given character component class identifier. + * Iterates over the {@link ActionSequence} instances representing + * every available action sequence. */ - public Iterator enumerateComponentsByClass (int clid); + public Iterator enumerateActionSequences (); + + /** + * Iterates over the component ids of all components in the specified + * class. + */ + public Iterator enumerateComponentIds (ComponentClass compClass); } diff --git a/src/java/com/threerings/cast/FrameProvider.java b/src/java/com/threerings/cast/FrameProvider.java new file mode 100644 index 000000000..9ee56c895 --- /dev/null +++ b/src/java/com/threerings/cast/FrameProvider.java @@ -0,0 +1,21 @@ +// +// $Id: FrameProvider.java,v 1.1 2001/11/27 08:09:35 mdb Exp $ + +package com.threerings.cast; + +import com.threerings.media.sprite.MultiFrameImage; + +/** + * Provides a mechanism where by a character component can obtain access + * to its image frames for a particular action in an on demand manner. + */ +public interface FrameProvider +{ + /** + * Returns the animation frames (in the eight sprite directions) for + * the specified action of the specified component. May return null if + * the specified action does not exist for the specified component. + */ + public MultiFrameImage[] getFrames ( + CharacterComponent component, String action); +} diff --git a/src/java/com/threerings/cast/NoSuchComponentException.java b/src/java/com/threerings/cast/NoSuchComponentException.java index cfc5b16d4..60c40b405 100644 --- a/src/java/com/threerings/cast/NoSuchComponentException.java +++ b/src/java/com/threerings/cast/NoSuchComponentException.java @@ -1,5 +1,5 @@ // -// $Id: NoSuchComponentException.java,v 1.1 2001/10/26 01:17:21 shaper Exp $ +// $Id: NoSuchComponentException.java,v 1.2 2001/11/27 08:09:35 mdb Exp $ package com.threerings.cast; @@ -9,16 +9,16 @@ package com.threerings.cast; */ public class NoSuchComponentException extends Exception { - public NoSuchComponentException (int cid) + public NoSuchComponentException (int componentId) { - super("No such component [cid=" + cid + "]"); - _cid = cid; + super("No such component [componentId=" + componentId + "]"); + _componentId = componentId; } - public int getId () + public int getComponentId () { - return _cid; + return _componentId; } - protected int _cid; + protected int _componentId; } diff --git a/src/java/com/threerings/cast/StandardActions.java b/src/java/com/threerings/cast/StandardActions.java new file mode 100644 index 000000000..0495ad6cd --- /dev/null +++ b/src/java/com/threerings/cast/StandardActions.java @@ -0,0 +1,19 @@ +// +// $Id: StandardActions.java,v 1.1 2001/11/27 08:09:35 mdb Exp $ + +package com.threerings.cast; + +/** + * Actions are referenced by name and this interface defines constants for + * two standard actions: standing and walking. Because character sprites + * follow paths, it is helpful for them to take care of switching between + * the standing and walking actions automatically. + */ +public interface StandardActions +{ + /** The name of the standard standing action. */ + public static final String STANDING = "standing"; + + /** The name of the standard walking action. */ + public static final String WALKING = "walking"; +} diff --git a/src/java/com/threerings/cast/builder/BuilderModel.java b/src/java/com/threerings/cast/builder/BuilderModel.java index 8d49be6d3..baa9ea835 100644 --- a/src/java/com/threerings/cast/builder/BuilderModel.java +++ b/src/java/com/threerings/cast/builder/BuilderModel.java @@ -1,29 +1,32 @@ // -// $Id: BuilderModel.java,v 1.3 2001/11/18 04:09:21 mdb Exp $ +// $Id: BuilderModel.java,v 1.4 2001/11/27 08:09:35 mdb Exp $ -package com.threerings.cast.tools.builder; +package com.threerings.cast.builder; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; import com.samskivert.util.CollectionUtil; -import com.samskivert.util.HashIntMap; -import com.threerings.cast.CharacterManager; +import com.threerings.cast.ComponentRepository; import com.threerings.cast.ComponentClass; /** - * The builder model represents the current state of the character - * the user is building. + * The builder model represents the current state of the character the + * user is building. */ public class BuilderModel { /** * Constructs a builder model. */ - public BuilderModel (CharacterManager charmgr) + public BuilderModel (ComponentRepository crepo) { - gatherComponentInfo(charmgr); - _selected = new int[_classes.size()]; + gatherComponentInfo(crepo); } /** @@ -58,28 +61,36 @@ public class BuilderModel } /** - * Returns a map of the components available for each component - * class, keyed on component class id. + * Returns the list of components available in the specified class. */ - public Map getComponents () + public List getComponents (ComponentClass cclass) { - return Collections.unmodifiableMap(_components); + List list = (List)_components.get(cclass); + if (list == null) { + list = new ArrayList(); + } + return list; } /** - * Returns an array of the currently selected component ids. + * Returns the selected components in an array. */ public int[] getSelectedComponents () { - return _selected; + int[] values = new int[_selected.size()]; + Iterator iter = _selected.values().iterator(); + for (int i = 0; iter.hasNext(); i++) { + values[i] = ((Integer)iter.next()).intValue(); + } + return values; } /** - * Sets the selected component for the given component class id. + * Sets the selected component for the given component class. */ - public void setSelectedComponent (int clid, int cid) + public void setSelectedComponent (ComponentClass cclass, int cid) { - _selected[clid] = cid; + _selected.put(cclass, new Integer(cid)); notifyListeners(BuilderModelListener.COMPONENT_CHANGED); } @@ -87,22 +98,21 @@ public class BuilderModel * Gathers component class and component information from the * character manager for later reference by others. */ - protected void gatherComponentInfo (CharacterManager charmgr) + protected void gatherComponentInfo (ComponentRepository crepo) { // get the list of all component classes - CollectionUtil.addAll(_classes, charmgr.enumerateComponentClasses()); + CollectionUtil.addAll(_classes, crepo.enumerateComponentClasses()); for (int ii = 0; ii < _classes.size(); ii++) { // get the list of components available for this class - int clid = ((ComponentClass)_classes.get(ii)).clid; - Iterator iter = charmgr.enumerateComponentsByClass(clid); + ComponentClass cclass = (ComponentClass)_classes.get(ii); + Iterator iter = crepo.enumerateComponentIds(cclass); while (iter.hasNext()) { Integer cid = (Integer)iter.next(); - - ArrayList clist = (ArrayList)_components.get(clid); + ArrayList clist = (ArrayList)_components.get(cclass); if (clist == null) { - _components.put(clid, clist = new ArrayList()); + _components.put(cclass, clist = new ArrayList()); } clist.add(cid); @@ -111,10 +121,10 @@ public class BuilderModel } /** The currently selected character components. */ - protected int _selected[]; + protected HashMap _selected = new HashMap(); /** The hashtable of available component ids for each class. */ - protected HashIntMap _components = new HashIntMap(); + protected HashMap _components = new HashMap(); /** The list of all available component classes. */ protected ArrayList _classes = new ArrayList(); diff --git a/src/java/com/threerings/cast/builder/BuilderModelListener.java b/src/java/com/threerings/cast/builder/BuilderModelListener.java index 56b4bce50..3b223cd6e 100644 --- a/src/java/com/threerings/cast/builder/BuilderModelListener.java +++ b/src/java/com/threerings/cast/builder/BuilderModelListener.java @@ -1,7 +1,7 @@ // -// $Id: BuilderModelListener.java,v 1.2 2001/11/18 04:09:21 mdb Exp $ +// $Id: BuilderModelListener.java,v 1.3 2001/11/27 08:09:35 mdb Exp $ -package com.threerings.cast.tools.builder; +package com.threerings.cast.builder; /** * The builder model listener interface should be implemented by diff --git a/src/java/com/threerings/cast/builder/BuilderPanel.java b/src/java/com/threerings/cast/builder/BuilderPanel.java index 572e10936..1d82daecf 100644 --- a/src/java/com/threerings/cast/builder/BuilderPanel.java +++ b/src/java/com/threerings/cast/builder/BuilderPanel.java @@ -1,30 +1,25 @@ // -// $Id: BuilderPanel.java,v 1.4 2001/11/18 04:09:21 mdb Exp $ +// $Id: BuilderPanel.java,v 1.5 2001/11/27 08:09:35 mdb Exp $ -package com.threerings.cast.tools.builder; - -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.util.Iterator; +package com.threerings.cast.builder; import javax.swing.*; - import com.samskivert.swing.*; -import com.samskivert.util.StringUtil; -import com.threerings.cast.*; +import com.threerings.cast.CharacterManager; +import com.threerings.cast.ComponentRepository; /** - * The builder panel presents the user with an overview of a - * composited character and facilities for altering the individual - * components that comprise the character's display image. + * The builder panel presents the user with an overview of a composited + * character and facilities for altering the individual components that + * comprise the character's display image. */ public class BuilderPanel extends JPanel { /** * Constructs the builder panel. */ - public BuilderPanel (CharacterManager charmgr) + public BuilderPanel (CharacterManager charmgr, ComponentRepository crepo) { setLayout(new VGroupLayout()); @@ -35,7 +30,7 @@ public class BuilderPanel extends JPanel gl.setOffAxisPolicy(GroupLayout.STRETCH); // create the builder model - BuilderModel model = new BuilderModel(charmgr); + BuilderModel model = new BuilderModel(crepo); // create the component selection and sprite display panels JPanel sub = new JPanel(gl); diff --git a/src/java/com/threerings/cast/builder/ClassEditor.java b/src/java/com/threerings/cast/builder/ClassEditor.java index d9ade9560..40f27f2d2 100644 --- a/src/java/com/threerings/cast/builder/ClassEditor.java +++ b/src/java/com/threerings/cast/builder/ClassEditor.java @@ -1,7 +1,7 @@ // -// $Id: ClassEditor.java,v 1.2 2001/11/18 04:09:21 mdb Exp $ +// $Id: ClassEditor.java,v 1.3 2001/11/27 08:09:35 mdb Exp $ -package com.threerings.cast.tools.builder; +package com.threerings.cast.builder; import java.util.List; @@ -17,20 +17,20 @@ import com.threerings.cast.Log; import com.threerings.cast.ComponentClass; /** - * The class editor displays a label and a slider that allow the user - * to select the desired component for a given component class. + * The class editor displays a label and a slider that allow the user to + * select the desired component for a given component class. */ public class ClassEditor extends JPanel implements ChangeListener { /** * Constructs a class editor. */ - public ClassEditor (BuilderModel model, ComponentClass cclass, - List components) + public ClassEditor ( + BuilderModel model, ComponentClass cclass, List components) { _model = model; _components = components; - _clid = cclass.clid; + _cclass = cclass; GroupLayout gl = new VGroupLayout(GroupLayout.STRETCH); gl.setOffAxisPolicy(GroupLayout.STRETCH); @@ -76,11 +76,11 @@ public class ClassEditor extends JPanel implements ChangeListener protected void setSelectedComponent (int idx) { int cid = ((Integer)_components.get(idx)).intValue(); - _model.setSelectedComponent(_clid, cid); + _model.setSelectedComponent(_cclass, cid); } - /** The component class id associated with this editor. */ - protected int _clid; + /** The component class associated with this editor. */ + protected ComponentClass _cclass; /** The components selectable via this editor. */ protected List _components; diff --git a/src/java/com/threerings/cast/builder/ComponentPanel.java b/src/java/com/threerings/cast/builder/ComponentPanel.java index fe1d552e5..d3fd261f9 100644 --- a/src/java/com/threerings/cast/builder/ComponentPanel.java +++ b/src/java/com/threerings/cast/builder/ComponentPanel.java @@ -1,7 +1,7 @@ // -// $Id: ComponentPanel.java,v 1.5 2001/11/18 04:09:21 mdb Exp $ +// $Id: ComponentPanel.java,v 1.6 2001/11/27 08:09:35 mdb Exp $ -package com.threerings.cast.tools.builder; +package com.threerings.cast.builder; import java.util.List; import java.util.Map; @@ -40,12 +40,10 @@ public class ComponentPanel extends JPanel protected void addClassEditors (BuilderModel model) { List classes = model.getComponentClasses(); - Map components = model.getComponents(); - int size = classes.size(); for (int ii = 0; ii < size; ii++) { ComponentClass cclass = (ComponentClass)classes.get(ii); - List ccomps = (List)components.get(new Integer(cclass.clid)); + List ccomps = model.getComponents(cclass); add(new ClassEditor(model, cclass, ccomps)); } } diff --git a/src/java/com/threerings/cast/builder/SpritePanel.java b/src/java/com/threerings/cast/builder/SpritePanel.java index e8d4394b8..b83c8e2db 100644 --- a/src/java/com/threerings/cast/builder/SpritePanel.java +++ b/src/java/com/threerings/cast/builder/SpritePanel.java @@ -1,7 +1,7 @@ // -// $Id: SpritePanel.java,v 1.3 2001/11/18 04:09:21 mdb Exp $ +// $Id: SpritePanel.java,v 1.4 2001/11/27 08:09:35 mdb Exp $ -package com.threerings.cast.tools.builder; +package com.threerings.cast.builder; import java.awt.*; import javax.swing.BorderFactory; diff --git a/src/java/com/threerings/cast/bundle/BundleUtil.java b/src/java/com/threerings/cast/bundle/BundleUtil.java new file mode 100644 index 000000000..af4e09e88 --- /dev/null +++ b/src/java/com/threerings/cast/bundle/BundleUtil.java @@ -0,0 +1,55 @@ +// +// $Id: BundleUtil.java,v 1.1 2001/11/27 08:09:35 mdb Exp $ + +package com.threerings.cast.bundle; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; + +import com.threerings.resource.ResourceBundle; + +/** + * Utility functions related to creating and manipulating component + * bundles. + */ +public class BundleUtil +{ + /** The path in the metadata bundle to the serialized action table. */ + public static final String ACTIONS_PATH = "actions.dat"; + + /** The path in the metadata bundle to the serialized action tile sets + * table. */ + public static final String ACTION_SETS_PATH = "action_sets.dat"; + + /** The path in the metadata bundle to the serialized component class + * table. */ + public static final String CLASSES_PATH = "classes.dat"; + + /** The path in the component bundle to the serialized component id to + * class/type mapping. */ + public static final String COMPONENTS_PATH = "components.dat"; + + /** The file extension of our action tile images. */ + public static final String IMAGE_EXTENSION = ".png"; + + /** + * Attempts to load an object from the supplied resource bundle with + * the specified path. + * + * @return the unserialized object in question or null if no + * serialized object data was available at the specified path. + * + * @exception IOException thrown if an I/O error occurs while reading + * the object from the bundle. + */ + public static Object loadObject (ResourceBundle bundle, String path) + throws IOException, ClassNotFoundException + { + InputStream bin = bundle.getResource(path); + if (bin == null) { + return null; + } + return new ObjectInputStream(bin).readObject(); + } +} diff --git a/src/java/com/threerings/cast/bundle/BundledComponentRepository.java b/src/java/com/threerings/cast/bundle/BundledComponentRepository.java new file mode 100644 index 000000000..998b38218 --- /dev/null +++ b/src/java/com/threerings/cast/bundle/BundledComponentRepository.java @@ -0,0 +1,303 @@ +// +// $Id: BundledComponentRepository.java,v 1.1 2001/11/27 08:09:35 mdb Exp $ + +package com.threerings.cast.bundle; + +import java.awt.Image; +import java.awt.image.BufferedImage; +import javax.imageio.ImageIO; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; + +import java.util.HashMap; +import java.util.Iterator; + +import com.samskivert.io.NestableIOException; +import com.samskivert.util.HashIntMap; +import com.samskivert.util.Tuple; + +import org.apache.commons.collections.FilterIterator; +import org.apache.commons.collections.Predicate; + +import com.threerings.resource.ResourceBundle; +import com.threerings.resource.ResourceManager; + +import com.threerings.media.sprite.MultiFrameImage; +import com.threerings.media.sprite.Sprite; + +import com.threerings.media.tile.ImageProvider; +import com.threerings.media.tile.TileSet; +import com.threerings.media.tile.NoSuchTileException; + +import com.threerings.cast.CharacterComponent; +import com.threerings.cast.ComponentClass; +import com.threerings.cast.ComponentRepository; +import com.threerings.cast.FrameProvider; +import com.threerings.cast.Log; +import com.threerings.cast.NoSuchComponentException; + +/** + * A component repository implementation that obtains information from + * resource bundles. + * + * @see ResourceManager + */ +public class BundledComponentRepository + implements ComponentRepository +{ + /** + * Constructs a repository which will obtain its resource set from the + * supplied resource manager. + * + * @param rmgr the resource manager from which to obtain our resource + * set. + * @param name the name of the resource set from which we will be + * loading our component data. + * + * @exception IOException thrown if an I/O error occurs while reading + * our metadata from the resource bundles. + */ + public BundledComponentRepository (ResourceManager rmgr, String name) + throws IOException + { + // first we obtain the resource set from whence will come our + // bundles + ResourceBundle[] rbundles = rmgr.getResourceSet(name); + + // look for our metadata info in each of the bundles + try { + for (int i = 0; i < rbundles.length; i++) { + if (_actions == null) { + _actions = (HashMap)BundleUtil.loadObject( + rbundles[i], BundleUtil.ACTIONS_PATH); + } + if (_actionSets == null) { + _actionSets = (HashMap)BundleUtil.loadObject( + rbundles[i], BundleUtil.ACTION_SETS_PATH); + } + if (_classes == null) { + _classes = (HashMap)BundleUtil.loadObject( + rbundles[i], BundleUtil.CLASSES_PATH); + } + } + + // now go back and load up all of the component information + for (int i = 0; i < rbundles.length; i++) { + HashIntMap comps = (HashIntMap)BundleUtil.loadObject( + rbundles[i], BundleUtil.COMPONENTS_PATH); + if (comps == null) { + continue; + } + + // create a frame provider for this bundle + FrameProvider fprov = new ResourceBundleProvider(rbundles[i]); + + // now create character component instances for each component + // in the serialized table + Iterator iter = comps.keySet().iterator(); + while (iter.hasNext()) { + int componentId = ((Integer)iter.next()).intValue(); + Tuple info = (Tuple)comps.get(componentId); + createComponent(componentId, (String)info.left, + (String)info.right, fprov); + } + } + + } catch (ClassNotFoundException cnfe) { + throw new NestableIOException( + "Internal error unserializing metadata", cnfe); + } + } + + // documentation inherited + public CharacterComponent getComponent (int componentId) + throws NoSuchComponentException + { + CharacterComponent component = (CharacterComponent) + _components.get(componentId); + if (component == null) { + throw new NoSuchComponentException(componentId); + } + return component; + } + + // documentation inherited + public Iterator enumerateComponentClasses () + { + return _classes.values().iterator(); + } + + // documentation inherited + public Iterator enumerateActionSequences () + { + return _actions.values().iterator(); + } + + // documentation inherited + public Iterator enumerateComponentIds (final ComponentClass compClass) + { + Predicate classP = new Predicate() { + public boolean evaluate (Object input) { + CharacterComponent comp = (CharacterComponent) + _components.get(input); + return comp.componentClass.equals(compClass); + } + }; + return new FilterIterator(_components.keySet().iterator(), classP); + } + + /** + * Creates a component and inserts it into the component table. + */ + protected void createComponent ( + int componentId, String cclass, String cname, FrameProvider fprov) + { + // look up the component class information + ComponentClass clazz = (ComponentClass)_classes.get(cclass); + if (clazz == null) { + Log.warning("Non-existent component class " + + "[class=" + cclass + ", name=" + cname + + ", id=" + componentId + "]."); + return; + } + + // create the component + CharacterComponent component = new CharacterComponent( + componentId, cname, clazz, fprov); + + // cache it + _components.put(componentId, component); + } + + /** + * Instances of these provide images to our component action tilesets + * and frames to our components. + */ + protected class ResourceBundleProvider + implements ImageProvider, FrameProvider + { + /** + * Constructs an instance that will obtain image data from the + * specified resource bundle. + */ + public ResourceBundleProvider (ResourceBundle bundle) + { + _bundle = bundle; + } + + // documentation inherited + public BufferedImage loadImage (String path) + throws IOException + { + // obtain the image data from our resource bundle + InputStream imgin = _bundle.getResource(path); + if (imgin == null) { + String errmsg = "No such image in resource bundle " + + "[bundle=" + _bundle + ", path=" + path + "]."; + throw new FileNotFoundException(errmsg); + } + return ImageIO.read(imgin); + } + + // documentation inherited + public MultiFrameImage[] getFrames ( + CharacterComponent component, String action) + { + // get the tileset for this action + TileSet aset = (TileSet)_actionSets.get(action); + if (aset == null) { + Log.warning("Can't provide animation frames for undefined " + + "action [action=" + action + + ", component=" + component + "]."); + return null; + } + + // construct the appropriate image path for the component + String path = component.componentClass.name + File.separator + + component.name + File.separator + action + + BundleUtil.IMAGE_EXTENSION; + + // clone the tileset with this new image path + try { + TileSet fset = aset.clone(path); + fset.setImageProvider(this); + + // and create the necessary multiframe image instances + MultiFrameImage[] frames = + new MultiFrameImage[Sprite.NUM_DIRECTIONS]; + for (int i = 0; i < frames.length; i++) { + frames[i] = new TileSetFrameImage(fset, i); + } + + return frames; + + } catch (CloneNotSupportedException cnse) { + Log.warning("Unable to clone tileset [action=" + action + + ", component=" + component + + ", set=" + aset + "]."); + return null; + } + } + + /** The resource bundle from which we obtain image data. */ + protected ResourceBundle _bundle; + } + + /** + * Used to provide multiframe images using data obtained from a + * tileset. + */ + protected static class TileSetFrameImage implements MultiFrameImage + { + /** + * Constructs a tileset frame image with the specified tileset and + * for the specified orientation. + */ + public TileSetFrameImage (TileSet set, int orientation) + { + _set = set; + _orient = orientation; + } + + // documentation inherited + public int getFrameCount () + { + return _set.getTileCount() / Sprite.NUM_DIRECTIONS; + } + + // documentation inherited + public Image getFrame (int index) + { + int tileIndex = _orient * Sprite.NUM_DIRECTIONS + index; + try { + return _set.getTile(tileIndex).getImage(); + } catch (NoSuchTileException nste) { + Log.warning("Can't extract action frame [set=" + _set + + ", orient=" + _orient + ", index=" + index + "]."); + return null; + } + } + + /** The tileset from which we obtain our frame images. */ + protected TileSet _set; + + /** The particular orientation for which we are providing + * frames. */ + protected int _orient; + } + + /** A table of action sequences. */ + protected HashMap _actions; + + /** A table of action sequence tilesets. */ + protected HashMap _actionSets; + + /** A table of component classes. */ + protected HashMap _classes; + + /** The component table. */ + protected HashIntMap _components = new HashIntMap(); +} diff --git a/src/java/com/threerings/cast/bundle/tools/ComponentBundlerTask.java b/src/java/com/threerings/cast/bundle/tools/ComponentBundlerTask.java new file mode 100644 index 000000000..59c5e2fed --- /dev/null +++ b/src/java/com/threerings/cast/bundle/tools/ComponentBundlerTask.java @@ -0,0 +1,263 @@ +// +// $Id: ComponentBundlerTask.java,v 1.1 2001/11/27 08:09:35 mdb Exp $ + +package com.threerings.cast.tools.bundle; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import java.util.ArrayList; +import java.util.HashMap; + +import java.util.jar.JarOutputStream; +import java.util.jar.JarEntry; + +import com.samskivert.io.PersistenceException; +import com.samskivert.util.HashIntMap; +import com.samskivert.util.StringUtil; +import com.samskivert.util.Tuple; + +import org.apache.commons.util.StreamUtils; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.FileSet; + +import com.threerings.cast.ComponentIDBroker; +import com.threerings.cast.bundle.BundleUtil; + +/** + * Ant task for creating component bundles. This task must be configured + * with a number of parameters: + * + *
+ * target=[path to bundle file, which will be created]
+ * mapfile=[path to the component map file which maintains a mapping from
+ *          component id to component class/name, it will be created the
+ *          first time and updated as new components are mapped]
+ * 
+ * + * It should also contain one or more nested <fileset> elements that + * enumerate the action tileset images that should be included in the + * component bundle. + */ +public class ComponentBundlerTask extends Task +{ + /** + * Sets the path to the bundle file that we'll be creating. + */ + public void setTarget (File target) + { + _target = target; + } + + /** + * Sets the path to the component map file that we'll use to obtain + * component ids for the bundled components. + */ + public void setMapfile (File mapfile) + { + _mapfile = mapfile; + } + + /** + * Adds a nested <fileset> element. + */ + public void addFileset (FileSet set) + { + _filesets.add(set); + } + + /** + * Performs the actual work of the task. + */ + public void execute () throws BuildException + { + // make sure everythign was set up properly + ensureSet(_target, "Must specify the path to the target bundle " + + "file via the 'file' attribute."); + ensureSet(_mapfile, "Must specify the path to the component map " + + "file via the 'mapfile' attribute."); + + // load up our component ID broker + ComponentIDBroker broker = loadBroker(_mapfile); + + try { + // make sure we can create our bundle file + FileOutputStream fout = new FileOutputStream(_target); + JarOutputStream jout = new JarOutputStream(fout); + + // we'll fill this with component id to tuple mappings as we go + HashIntMap mapping = new HashIntMap(); + + // deal with the filesets + for (int i = 0; i < _filesets.size(); i++) { + FileSet fs = (FileSet)_filesets.get(i); + DirectoryScanner ds = fs.getDirectoryScanner(project); + File fromDir = fs.getDir(project); + String[] srcFiles = ds.getIncludedFiles(); + + for (int f = 0; f < srcFiles.length; f++) { + File cfile = new File(fromDir, srcFiles[f]); + // determine the [class, name, action] triplet + String[] info = decomposePath(cfile.getPath()); + // obtain the component id from our id broker + int cid = broker.getComponentID(info[0], info[1]); + // add a mapping for this component + mapping.put(cid, new Tuple(info[0], info[1])); + // construct the path that'll go in the jar file and + // stuff the component into the jarfile + jout.putNextEntry(new JarEntry(composePath(info))); + StreamUtils.pipe(new FileInputStream(cfile), jout); + } + } + + // write our mapping table to the jar file as well + jout.putNextEntry(new JarEntry(BundleUtil.COMPONENTS_PATH)); + ObjectOutputStream oout = new ObjectOutputStream(jout); + oout.writeObject(mapping); + oout.flush(); + + // seal up our jar file + jout.close(); + + } catch (IOException ioe) { + String errmsg = "Unable to create component bundle."; + throw new BuildException(errmsg, ioe); + + } catch (PersistenceException pe) { + String errmsg = "Unable to obtain component ID mapping."; + throw new BuildException(errmsg, pe); + } + + // save our updated component ID broker + saveBroker(_mapfile, broker); + } + + /** + * Decomposes the full path to a component image into a [class, name, + * action] triplet. + */ + protected String[] decomposePath (String path) + throws BuildException + { + // first strip off the file extension + if (!path.endsWith(BundleUtil.IMAGE_EXTENSION)) { + throw new BuildException("Can't bundle malformed image file " + + "[path=" + path + "]."); + } + path = path.substring(0, path.length() - + BundleUtil.IMAGE_EXTENSION.length()); + + // now decompose the path + String[] components = StringUtil.split(path, File.separator); + int clen = components.length; + if (clen < 3) { + throw new BuildException("Can't bundle malformed image file " + + "[path=" + path + "]."); + } + String[] info = new String[3]; + System.arraycopy(components, clen-3, info, 0, 3); + return info; + } + + /** + * Composes a triplet of [class, name, action] into the path that + * should be supplied to the JarEntry that contains the associated + * image data. + */ + protected String composePath (String[] info) + { + return (info[0] + File.separator + info[1] + File.separator + + info[2] + BundleUtil.IMAGE_EXTENSION); + } + + protected void ensureSet (Object value, String errmsg) + throws BuildException + { + if (value == null) { + throw new BuildException(errmsg); + } + } + + /** + * Loads the hashmap ID broker from its persistent representation in + * the specified file. + */ + protected HashMapIDBroker loadBroker (File mapfile) + throws BuildException + { + HashMapIDBroker broker; + + try { + FileInputStream fin = new FileInputStream(mapfile); + ObjectInputStream oin = new ObjectInputStream(fin); + broker = (HashMapIDBroker)oin.readObject(); + + } catch (FileNotFoundException fnfe) { + // if the file doesn't yet exist, start with a blank broker + broker = new HashMapIDBroker(); + + } catch (Exception e) { + throw new BuildException("Error loading component ID map.", e); + } + + return broker; + } + + /** + * Stores a persistent representation of the supplied hashmap ID + * broker in the specified file. + */ + protected void saveBroker (File mapfile, ComponentIDBroker broker) + throws BuildException + { + try { + FileOutputStream fout = new FileOutputStream(mapfile); + ObjectOutputStream oout = new ObjectOutputStream(fout); + oout.writeObject(broker); + oout.close(); + } catch (IOException ioe) { + throw new BuildException("Unable to store component ID map.", ioe); + } + } + + protected static class HashMapIDBroker + extends HashMap implements ComponentIDBroker + { + public int getComponentID (String cclass, String cname) + throws PersistenceException + { + Tuple key = new Tuple(cclass, cname); + Integer cid = (Integer)get(key); + if (cid == null) { + cid = new Integer(++_nextCID); + put(key, cid); + } + return cid.intValue(); + } + + public void commit () + throws PersistenceException + { + // nothing doing + } + + protected int _nextCID = 0; + } + + /** The path to our component bundle file. */ + protected File _target; + + /** The path to our component map file. */ + protected File _mapfile; + + /** A list of filesets that contain tile images. */ + protected ArrayList _filesets = new ArrayList(); +} diff --git a/src/java/com/threerings/cast/bundle/tools/MetadataBundlerTask.java b/src/java/com/threerings/cast/bundle/tools/MetadataBundlerTask.java new file mode 100644 index 000000000..100508c53 --- /dev/null +++ b/src/java/com/threerings/cast/bundle/tools/MetadataBundlerTask.java @@ -0,0 +1,241 @@ +// +// $Id: MetadataBundlerTask.java,v 1.1 2001/11/27 08:09:35 mdb Exp $ + +package com.threerings.cast.tools.bundle; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectOutputStream; + +import java.util.ArrayList; +import java.util.HashMap; + +import java.util.jar.JarOutputStream; +import java.util.jar.JarEntry; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +import org.xml.sax.SAXException; +import org.apache.commons.digester.Digester; + +import com.samskivert.util.Tuple; + +import com.threerings.media.tile.TileSet; +import com.threerings.media.tools.tile.xml.SwissArmyTileSetRuleSet; + +import com.threerings.cast.ActionSequence; +import com.threerings.cast.ComponentClass; +import com.threerings.cast.bundle.BundleUtil; + +import com.threerings.cast.tools.xml.ActionRuleSet; +import com.threerings.cast.tools.xml.ClassRuleSet; + +/** + * Ant task for creating metadata bundles, which contain action sequence + * and component class definition information. This task must be + * configured with a number of parameters: + * + *
+ * actiondef=[path to actions.xml]
+ * classdef=[path to classes.xml]
+ * file=[path to metadata bundle, which will be created]
+ * 
+ */ +public class MetadataBundlerTask extends Task +{ + public void setActiondef (String actiondef) + { + _actionDef = actiondef; + } + + public void setClassdef (String classdef) + { + _classDef = classdef; + } + + public void setTarget (File target) + { + _target = target; + } + + /** + * Performs the actual work of the task. + */ + public void execute () + throws BuildException + { + // make sure everythign was set up properly + ensureSet(_actionDef, "Must specify the action sequence " + + "definitions via the 'actiondef' attribute."); + ensureSet(_classDef, "Must specify the component class definitions " + + "via the 'classdef' attribute."); + ensureSet(_target, "Must specify the path to the target bundle " + + "file via the 'target' attribute."); + + // make sure we can write to the target bundle file + FileOutputStream fout = null; + try { + fout = new FileOutputStream(_target); + + // parse our metadata + Tuple tuple = parseActions(); + HashMap actions = (HashMap)tuple.left; + HashMap actionSets = (HashMap)tuple.right; + HashMap classes = parseClasses(); + + // and create the bundle file + JarOutputStream jout = new JarOutputStream(fout); + + // throw the serialized actions table in there + JarEntry aentry = new JarEntry(BundleUtil.ACTIONS_PATH); + jout.putNextEntry(aentry); + ObjectOutputStream oout = new ObjectOutputStream(jout); + oout.writeObject(actions); + oout.flush(); + + // throw the serialized action tilesets table in there + JarEntry sentry = new JarEntry(BundleUtil.ACTION_SETS_PATH); + jout.putNextEntry(sentry); + oout = new ObjectOutputStream(jout); + oout.writeObject(actionSets); + oout.flush(); + + // throw the serialized classes table in there + JarEntry centry = new JarEntry(BundleUtil.CLASSES_PATH); + jout.putNextEntry(centry); + oout = new ObjectOutputStream(jout); + oout.writeObject(classes); + oout.flush(); + + // close it up and we're done + jout.close(); + + } catch (IOException ioe) { + String errmsg = "Unable to output to target bundle " + + "[path=" + _target.getPath() + "]."; + throw new BuildException(errmsg, ioe); + + } finally { + if (fout != null) { + try { + fout.close(); + } catch (IOException ioe) { + // nothing to complain about here + } + } + } + } + + protected Tuple parseActions () + throws BuildException + { + // scan through the XML once to read the actions + Digester digester = new Digester(); + ActionRuleSet arules = new ActionRuleSet(); + arules.setPrefix("actions"); + digester.addRuleSet(arules); + digester.addSetNext("actions" + ActionRuleSet.ACTION_PATH, + "add", Object.class.getName()); + ArrayList actlist = parseList(digester, _actionDef); + + // now go through a second time reading the tileset info + digester = new Digester(); + SwissArmyTileSetRuleSet srules = new SwissArmyTileSetRuleSet(); + srules.setPrefix("actions" + ActionRuleSet.ACTION_PATH); + digester.addRuleSet(srules); + digester.addSetNext("actions" + ActionRuleSet.ACTION_PATH + + SwissArmyTileSetRuleSet.TILESET_PATH, "add", + Object.class.getName()); + ArrayList setlist = parseList(digester, _actionDef); + + // sanity check + if (actlist.size() != setlist.size()) { + String errmsg = "An action is missing its tileset " + + "definition, or something even wackier is going on."; + throw new BuildException(errmsg); + } + + // now create our mappings + HashMap actmap = new HashMap(); + HashMap setmap = new HashMap(); + + // create the action map + for (int i = 0; i < setlist.size(); i++) { + TileSet set = (TileSet)setlist.get(i); + ActionSequence act = (ActionSequence)actlist.get(i); + actmap.put(act.name, act); + setmap.put(act.name, set); + } + + return new Tuple(actmap, setmap); + } + + protected HashMap parseClasses () + throws BuildException + { + // load up our action and class info + Digester digester = new Digester(); + + // add our action rule set and a a rule to grab parsed actions + ClassRuleSet crules = new ClassRuleSet(); + crules.setPrefix("classes"); + digester.addRuleSet(crules); + digester.addSetNext("classes" + ClassRuleSet.CLASS_PATH, + "add", Object.class.getName()); + + ArrayList setlist = parseList(digester, _classDef); + HashMap clmap = new HashMap(); + + // create the action map + for (int i = 0; i < setlist.size(); i++) { + ComponentClass cl = (ComponentClass)setlist.get(i); + clmap.put(cl.name, cl); + } + + return clmap; + } + + protected ArrayList parseList (Digester digester, String path) + throws BuildException + { + try { + FileInputStream fin = new FileInputStream(path); + BufferedInputStream bin = new BufferedInputStream(fin); + + ArrayList setlist = new ArrayList(); + digester.push(setlist); + + // now fire up the digester to parse the stream + try { + digester.parse(bin); + } catch (Exception e) { + throw new BuildException("Parsing error.", e); + } + + return setlist; + + } catch (FileNotFoundException fnfe) { + String errmsg = "Unable to load metadata definition file " + + "[path=" + path + "]."; + throw new BuildException(errmsg, fnfe); + } + } + + protected void ensureSet (Object value, String errmsg) + throws BuildException + { + if (value == null) { + throw new BuildException(errmsg); + } + } + + protected String _actionDef; + protected String _classDef; + protected File _target; +} diff --git a/src/java/com/threerings/cast/tools/xml/ActionRuleSet.java b/src/java/com/threerings/cast/tools/xml/ActionRuleSet.java new file mode 100644 index 000000000..3513a3baa --- /dev/null +++ b/src/java/com/threerings/cast/tools/xml/ActionRuleSet.java @@ -0,0 +1,85 @@ +// +// $Id: ActionRuleSet.java,v 1.1 2001/11/27 08:09:35 mdb Exp $ + +package com.threerings.cast.tools.xml; + +import org.apache.commons.digester.Digester; +import org.apache.commons.digester.RuleSetBase; + +import com.samskivert.util.StringUtil; + +import com.samskivert.xml.CallMethodSpecialRule; +import com.samskivert.xml.SetFieldRule; +import com.samskivert.xml.SetPropertyFieldsRule; + +import com.threerings.cast.ActionSequence; + +/** + * The action rule set is used to parse the attributes of an action + * sequence instance. + */ +public class ActionRuleSet extends RuleSetBase +{ + /** The component of the digester path that is appended by the action + * rule set to match a action. This is appended to whatever prefix is + * provided to the action rule set to obtain the complete XML path to + * a matched action. */ + public static final String ACTION_PATH = "/action"; + + /** + * Instructs the action rule set to match actions with the supplied + * prefix. For example, passing a prefix of actions will + * match actions in the following XML file: + * + *
+     * <actions>
+     *   <action>
+     *     // ...
+     *   </action>
+     * </actions>
+     * 
+ * + * This must be called before adding the ruleset to a digester. + */ + public void setPrefix (String prefix) + { + _prefix = prefix; + } + + /** + * Adds the necessary rules to the digester to parse our actions. + */ + public void addRuleInstances (Digester digester) + { + // this creates the appropriate instance when we encounter a + // tag + digester.addObjectCreate(_prefix + ACTION_PATH, + ActionSequence.class.getName()); + + // grab the name attribute from the tag + digester.addRule(_prefix + ACTION_PATH, + new SetPropertyFieldsRule(digester)); + + // grab the other attributes from their respective tags + digester.addRule(_prefix + ACTION_PATH + "/framesPerSecond", + new SetFieldRule(digester, "framesPerSecond")); + + CallMethodSpecialRule origin = new CallMethodSpecialRule(digester) { + public void parseAndSet (String bodyText, Object target) + throws Exception { + int[] coords = StringUtil.parseIntArray(bodyText); + if (coords.length != 2) { + String errmsg = "Invalid specification '" + + bodyText + "'."; + throw new Exception(errmsg); + } + ((ActionSequence)target).origin.setLocation( + coords[0], coords[1]); + } + }; + digester.addRule(_prefix + ACTION_PATH + "/origin", origin); + } + + /** The prefix at which me match our actions. */ + protected String _prefix; +} diff --git a/src/java/com/threerings/cast/tools/xml/ClassRuleSet.java b/src/java/com/threerings/cast/tools/xml/ClassRuleSet.java new file mode 100644 index 000000000..bf6ff0a2c --- /dev/null +++ b/src/java/com/threerings/cast/tools/xml/ClassRuleSet.java @@ -0,0 +1,60 @@ +// +// $Id: ClassRuleSet.java,v 1.1 2001/11/27 08:09:35 mdb Exp $ + +package com.threerings.cast.tools.xml; + +import org.apache.commons.digester.Digester; +import org.apache.commons.digester.RuleSetBase; + +import com.samskivert.xml.SetPropertyFieldsRule; + +import com.threerings.cast.ComponentClass; + +/** + * The class rule set is used to parse the attributes of a component class + * instance. + */ +public class ClassRuleSet extends RuleSetBase +{ + /** The component of the digester path that is appended by the class + * rule set to match a component class. This is appended to whatever + * prefix is provided to the class rule set to obtain the complete XML + * path to a matched class. */ + public static final String CLASS_PATH = "/class"; + + /** + * Instructs the class rule set to match component classes with the + * supplied prefix. For example, passing a prefix of + * classes will match classes in the following XML file: + * + *
+     * <classes>
+     *   <class .../>
+     * </classes>
+     * 
+ * + * This must be called before adding the ruleset to a digester. + */ + public void setPrefix (String prefix) + { + _prefix = prefix; + } + + /** + * Adds the necessary rules to the digester to parse our classes. + */ + public void addRuleInstances (Digester digester) + { + // this creates the appropriate instance when we encounter a + // tag + digester.addObjectCreate(_prefix + CLASS_PATH, + ComponentClass.class.getName()); + + // grab the attributes from the tag + digester.addRule(_prefix + CLASS_PATH, + new SetPropertyFieldsRule(digester)); + } + + /** The prefix at which me match our component classes. */ + protected String _prefix; +} diff --git a/src/java/com/threerings/cast/tools/xml/XMLComponentParser.java b/src/java/com/threerings/cast/tools/xml/XMLComponentParser.java deleted file mode 100644 index 0def5595e..000000000 --- a/src/java/com/threerings/cast/tools/xml/XMLComponentParser.java +++ /dev/null @@ -1,249 +0,0 @@ -// -// $Id: XMLComponentParser.java,v 1.5 2001/11/20 04:15:43 mdb Exp $ - -package com.threerings.cast.tools.xml; - -import java.awt.Point; -import java.io.IOException; -import java.util.Arrays; -import java.util.HashMap; - -import org.xml.sax.*; - -import com.samskivert.util.*; -import com.samskivert.xml.SimpleParser; - -import com.threerings.media.tile.TileSet; -import com.threerings.media.tools.tile.xml.XMLTileSetParser; - -import com.threerings.cast.Log; -import com.threerings.cast.ActionSequence; -import com.threerings.cast.ComponentClass; -import com.threerings.cast.CharacterComponent; - -/** - * Parses an XML character component description file and populates - * hashtables with {@link ActionSequence}, {@link ComponentClass}, and the - * information necessary to construct {@link CharacterComponent} objects. - * - *

Does not currently perform validation on the input XML stream, - * though the parsing code assumes the XML document is well-formed. - */ -public class XMLComponentParser extends SimpleParser -{ - // documentation inherited - public void startElement ( - String uri, String localName, String qName, Attributes attributes) - { - if (qName.equals("charactercomponents")) { - // save off the image directory - _imagedir = attributes.getValue("imagedir"); - // load the tile sets - loadTileSets(attributes.getValue("tilesets")); - - } else if (qName.equals("action")) { - // construct and save off the action sequence object - ActionSequence as = getActionSequence(attributes); - _actions.put(as.asid, as); - - } else if (qName.equals("class")) { - // construct and save off the component class object - ComponentClass cclass = getComponentClass(attributes); - _classes.put(cclass.clid, cclass); - - } else if (qName.equals("component")) { - // construct and save off the character component - CharacterComponent component = getComponent(attributes); - _components.put(component.getId(), component); - } - } - - /** - * Loads the component descriptions in the given file into the - * given component data hashtables. - */ - public void loadComponents (String file, HashIntMap actions, - HashIntMap classes, HashIntMap components) - throws IOException - { - // save off hashtables for reference while parsing - _actions = actions; - _classes = classes; - _components = components; - - // clear out remnants of any previous parsing antics - _imagedir = null; - _tilesets.clear(); - - // parse the file - parseFile(file); - } - - /** - * Returns the image directory the component tile images reside - * within. - */ - public String getImageDir () - { - return _imagedir; - } - - /** - * Loads the tile sets in the XML file at the given path into the - * tile sets available for reference by the action sequences. - */ - protected void loadTileSets (String path) - { - // make sure we have a reasonable path - if (path == null) { - Log.warning("Null component tile set description file path."); - return; - } - - // parse the tile set description file - XMLTileSetParser p = new XMLTileSetParser(); - try { - p.loadTileSets(path, _tilesets); - } catch (IOException e) { - Log.warning("Exception loading component tile set descriptions " + - "[path=" + path + "]."); - } - } - - /** - * Returns a new action sequence object as described by the given - * attributes. - */ - protected ActionSequence getActionSequence (Attributes attrs) - { - ActionSequence as = new ActionSequence(); - - as.asid = parseInt(attrs.getValue("asid")); - as.name = attrs.getValue("name"); - as.fileid = attrs.getValue("fileid"); - - String tileset = attrs.getValue("tileset"); - as.tileset = (TileSet)_tilesets.get(tileset); - if (as.tileset == null) { - Log.warning("Action sequence references non-existent " + - "tile set [asid=" + as.asid + - ", tileset=" + tileset + "]."); - } - - as.fps = parseInt(attrs.getValue("fps")); - parsePoint(attrs.getValue("origin"), as.origin); - - return as; - } - - /** - * Returns a new component class object as described by the given - * attributes. - */ - protected ComponentClass getComponentClass (Attributes attrs) - { - ComponentClass cclass = new ComponentClass(); - cclass.clid = parseInt(attrs.getValue("clid")); - cclass.name = attrs.getValue("name"); - cclass.render = parseInt(attrs.getValue("render")); - return cclass; - } - - /** - * Returns a new character component object as described by the - * given attributes. - */ - protected CharacterComponent getComponent (Attributes attrs) - { - // retrieve the component attributes - int cid = parseInt(attrs.getValue("cid")); - String fileid = attrs.getValue("fileid"); - String val = attrs.getValue("asids"); - int asids[] = StringUtil.parseIntArray(val); - int clid = parseInt(attrs.getValue("clid")); - ComponentClass cclass = (ComponentClass)_classes.get(clid); - - // gather the array of relevant action sequences - ActionSequence seqs[] = getActionSequences(asids); - - // construct the component sans frames for now - CharacterComponent component = - new CharacterComponent(cid, fileid, seqs, cclass); - - // warn if the component has notably invalid attributes - validateComponent(cid, asids, clid); - - return component; - } - - /** - * Outputs a warning regarding the given component id if the given - * action sequence ids and component class id for that component - * don't exist. - */ - protected void validateComponent (int cid, int asids[], int clid) - { - // check the action sequence ids - for (int ii = 0; ii < asids.length; ii++) { - int asid = asids[ii]; - if (_actions.get(asid) == null) { - Log.warning( - "Component references non-existent action sequence " + - "[cid=" + cid + ", asid=" + asid + "]."); - } - } - - // check the class id - if (_classes.get(clid) == null) { - Log.warning("Component references non-existent class " + - "[cid=" + cid + ", clid=" + clid + "]."); - } - } - - /** - * Returns an array of action sequence objects corresponding to - * the action sequence ids in the given array. - */ - protected ActionSequence[] getActionSequences (int asids[]) - { - // sort the action sequence ids for better regularity - Arrays.sort(asids); - - // create and populate the array - ActionSequence[] seqs = new ActionSequence[asids.length]; - for (int ii = 0; ii < asids.length; ii++) { - seqs[ii] = (ActionSequence)_actions.get(asids[ii]); - } - - return seqs; - } - - /** - * Converts a string containing values as (x, y) into the - * corresponding integer values and populates the given point - * object. - * - * @param str the point values in string format. - * @param point the point object to populate. - */ - protected void parsePoint (String str, Point point) - { - int vals[] = StringUtil.parseIntArray(str); - point.setLocation(vals[0], vals[1]); - } - - /** The image directory containing the component image files. */ - protected String _imagedir; - - /** The hashtable of component tile sets. */ - protected HashMap _tilesets = new HashMap(); - - /** The hashtable of action sequences gathered while parsing. */ - protected HashIntMap _actions; - - /** The hashtable of component classes gathered while parsing. */ - protected HashIntMap _classes; - - /** The hashtable of character components gathered while parsing. */ - protected HashIntMap _components; -} diff --git a/src/java/com/threerings/cast/tools/xml/XMLComponentRepository.java b/src/java/com/threerings/cast/tools/xml/XMLComponentRepository.java deleted file mode 100644 index 350dc0a57..000000000 --- a/src/java/com/threerings/cast/tools/xml/XMLComponentRepository.java +++ /dev/null @@ -1,155 +0,0 @@ -// -// $Id: XMLComponentRepository.java,v 1.7 2001/11/18 04:09:21 mdb Exp $ - -package com.threerings.cast.tools.xml; - -import java.io.IOException; -import java.util.Collections; -import java.util.Iterator; - -import com.samskivert.util.Config; -import com.samskivert.util.HashIntMap; - -import com.threerings.cast.*; -import com.threerings.cast.util.TileUtil; - -import com.threerings.media.ImageManager; -import com.threerings.media.tile.TileManager; - -import com.threerings.miso.Log; -import com.threerings.miso.util.MisoUtil; - -/** - * The xml component repository provides access to character - * components obtained from component descriptions stored in an XML - * file. - */ -public class XMLComponentRepository implements ComponentRepository -{ - /** - * Constructs an xml component repository. - */ - public XMLComponentRepository (Config config) - { - // load component types and components - String file = config.getValue(COMPONENTS_KEY, DEFAULT_COMPONENTS); - try { - XMLComponentParser p = new XMLComponentParser(); - p.loadComponents(file, _actions, _classes, _components); - _imagedir = p.getImageDir(); - - } catch (IOException ioe) { - Log.warning("Exception loading component descriptions " + - "[ioe=" + ioe + "]."); - } - } - - // documentation inherited - public CharacterComponent getComponent (int cid) - throws NoSuchComponentException - { - // get the component information - CharacterComponent c = (CharacterComponent)_components.get(cid); - if (c == null) { - throw new NoSuchComponentException(cid); - } - - // get the character animation frames - c.setFrames(TileUtil.getComponentFrames(_imagedir, c)); - - return c; - } - - // documentation inherited - public Iterator enumerateComponentClasses () - { - return Collections.unmodifiableMap(_classes).values().iterator(); - } - - // documentation inherited - public Iterator enumerateComponentsByClass (int clid) - { - return new ComponentIterator(clid); - } - - /** - * Iterates over all components of a specified component type, and - * optionally a specified component class, in the component - * hashtable. - */ - protected class ComponentIterator implements Iterator - { - /** - * Constructs an iterator that iterates over all components of - * the specified component class. - */ - public ComponentIterator (int clid) - { - _clid = clid; - _iter = _components.keys(); - advance(); - } - - public boolean hasNext () - { - return (_next != null); - } - - public Object next () - { - Object next = _next; - advance(); - return next; - } - - public void remove () - { - throw new UnsupportedOperationException(); - } - - protected void advance () - { - while (_iter.hasNext()) { - Integer cid = (Integer)_iter.next(); - - CharacterComponent c = - (CharacterComponent)_components.get(cid); - if (c.getComponentClass().clid == _clid) { - _next = cid; - return; - } - } - _next = null; - } - - /** The component class id for inclusion in the iterator. */ - protected int _clid; - - /** The next character component associated with this - * iterator, or null if no more exist. */ - protected Object _next; - - /** The iterator over all components in the repository. */ - protected Iterator _iter; - } - - /** The config key for the character description file. */ - protected static final String COMPONENTS_KEY = - MisoUtil.CONFIG_KEY + ".components"; - - /** The default character description file. */ - protected static final String DEFAULT_COMPONENTS = - "rsrc/config/miso/components.xml"; - - /** The image directory containing the component image files. */ - protected String _imagedir; - - /** The hashtable of component types. */ - protected HashIntMap _actions = new HashIntMap(); - - /** The hashtable of component classes. */ - protected HashIntMap _classes = new HashIntMap(); - - /** The hashtable of character components. */ - protected HashIntMap _components = new HashIntMap(); -} diff --git a/src/java/com/threerings/cast/util/TileUtil.java b/src/java/com/threerings/cast/util/TileUtil.java index 5cf846b00..76754c311 100644 --- a/src/java/com/threerings/cast/util/TileUtil.java +++ b/src/java/com/threerings/cast/util/TileUtil.java @@ -1,16 +1,17 @@ // -// $Id: TileUtil.java,v 1.3 2001/11/18 04:09:21 mdb Exp $ +// $Id: TileUtil.java,v 1.4 2001/11/27 08:09:35 mdb Exp $ package com.threerings.cast.util; import java.awt.Image; import java.awt.image.BufferedImage; -import com.threerings.media.sprite.*; -import com.threerings.media.tile.*; +import com.threerings.media.sprite.MultiFrameImage; +import com.threerings.media.sprite.Sprite; +// import com.threerings.media.tile.*; import com.threerings.cast.Log; -import com.threerings.cast.*; +// import com.threerings.cast.*; /** * Miscellaneous tile-related utility functions. @@ -18,126 +19,105 @@ import com.threerings.cast.*; public class TileUtil { /** - * Renders each of the given src component frames - * into the corresponding frames of dest, allocating - * blank image frames for dest if none yet exist. + * Renders each of the given src component frames into + * the corresponding frames of dest, allocating blank + * image frames for dest if none yet exist. + * + * @throws IllegalArgumentException if the frame count of the source + * and destination images don't match. */ public static void compositeFrames ( - MultiFrameImage[][] dest, MultiFrameImage[][] src) + MultiFrameImage[] dest, MultiFrameImage[] src) { - for (int ii = 0; ii < dest.length; ii++) { - for (int orient = 0; orient < Sprite.NUM_DIRECTIONS; orient++) { - // create blank destination frames if needed - if (dest[ii][orient] == null) { - dest[ii][orient] = createBlankFrames(src[ii][orient]); - } + for (int orient = 0; orient < Sprite.NUM_DIRECTIONS; orient++) { + MultiFrameImage sframes = src[orient]; + MultiFrameImage dframes = dest[orient]; - // slap the images together - compositeFrames(dest[ii][orient], src[ii][orient]); + // create blank destination frames if needed + if (dframes == null) { + dest[orient] = new BlankFrameImage(sframes); + } + + // sanity check + int dsize = dframes.getFrameCount(); + int ssize = sframes.getFrameCount(); + if (dsize != ssize) { + String errmsg = "Can't composite images with inequal " + + "frame counts [dest=" + dsize + ", src=" + ssize + "]."; + throw new IllegalArgumentException(errmsg); + } + + // slap the images together + for (int ii = 0; ii < dsize; ii++) { + Image dimg = dframes.getFrame(ii); + Image simg = sframes.getFrame(ii); + dimg.getGraphics().drawImage(simg, 0, 0, null); } } } - /** - * Returns a new {@link MultiFrameImage} that has empty images in - * all frames. The number of frames in the resulting multi frame - * image is the same as the frame count for src. - */ - public static MultiFrameImage createBlankFrames (MultiFrameImage src) - { - // TODO: for now, just use the first frame from the source to - // get the width and height for all of the blank frames. fix - // this hack soon. - Image img = src.getFrame(0); - int wid = img.getWidth(null), hei = img.getHeight(null); - return new BlankFrameImage(wid, hei, src.getFrameCount()); - } +// /** +// * Returns a two-dimensional array of multi frame images containing +// * the frames of animation used to render the sprite while standing or +// * walking in each of the directions it may face. +// */ +// public static MultiFrameImage[][] getComponentFrames ( +// String imagedir, CharacterComponent c) +// { +// ActionSequence seqs[] = c.getActionSequences(); +// MultiFrameImage frames[][] = +// new MultiFrameImage[seqs.length][Sprite.NUM_DIRECTIONS]; - /** - * Returns a two-dimensional array of multi frame images - * containing the frames of animation used to render the sprite - * while standing or walking in each of the directions it may - * face. - */ - public static MultiFrameImage[][] getComponentFrames ( - String imagedir, CharacterComponent c) - { - ActionSequence seqs[] = c.getActionSequences(); - MultiFrameImage frames[][] = - new MultiFrameImage[seqs.length][Sprite.NUM_DIRECTIONS]; +// try { +// for (int ii = 0; ii < seqs.length; ii++) { +// ActionSequence as = seqs[ii]; - try { - for (int ii = 0; ii < seqs.length; ii++) { - ActionSequence as = seqs[ii]; +// // get the tile set containing the component tiles for +// // this action sequence +// String file = getImageFile(imagedir, as, c); +// TileSet tset = null; - // get the tile set containing the component tiles for - // this action sequence - String file = getImageFile(imagedir, as, c); - TileSet tset = null; +// try { +// tset = as.tileset.clone(file); +// } catch (CloneNotSupportedException e) { +// Log.warning("Failed to clone tile set " + +// "[tset=" + as.tileset + "]."); +// return null; +// } - try { - tset = as.tileset.clone(file); - } catch (CloneNotSupportedException e) { - Log.warning("Failed to clone tile set " + - "[tset=" + as.tileset + "]."); - return null; - } +// // get the number of frames of animation +// int frameCount = tset.getTileCount() / Sprite.NUM_DIRECTIONS; - // get the number of frames of animation - int frameCount = tset.getTileCount() / Sprite.NUM_DIRECTIONS; +// for (int dir = 0; dir < Sprite.NUM_DIRECTIONS; dir++) { +// // retrieve all images for the sequence and direction +// Image imgs[] = new Image[frameCount]; +// for (int jj = 0; jj < frameCount; jj++) { +// int idx = (dir * frameCount) + jj; +// imgs[jj] = tset.getTileImage(idx); +// } - for (int dir = 0; dir < Sprite.NUM_DIRECTIONS; dir++) { - // retrieve all images for the sequence and direction - Image imgs[] = new Image[frameCount]; - for (int jj = 0; jj < frameCount; jj++) { - int idx = (dir * frameCount) + jj; - imgs[jj] = tset.getTileImage(idx); - } +// // create the multi frame image +// frames[ii][dir] = new MultiFrameImageImpl(imgs); +// } +// } - // create the multi frame image - frames[ii][dir] = new MultiFrameImageImpl(imgs); - } - } +// } catch (TileException te) { +// Log.warning("Exception retrieving character images " + +// "[te=" + te + "]."); +// return null; +// } - } catch (TileException te) { - Log.warning("Exception retrieving character images " + - "[te=" + te + "]."); - return null; - } +// return frames; +// } - return frames; - } - - /** - * Returns the file path for the given action sequence and component. - */ - protected static String getImageFile ( - String imagedir, ActionSequence as, CharacterComponent c) - { - return imagedir + as.fileid + "_" + c.getFileId() + IMAGE_SUFFIX; - } - - /** - * Renders each of the given src frames into the - * corresponding frames of dest. - */ - protected static void compositeFrames ( - MultiFrameImage dest, MultiFrameImage src) - { - int dsize = dest.getFrameCount(), ssize = src.getFrameCount(); - if (dsize != ssize) { - Log.warning( - "Can't composite images with differing frame counts " + - "[dest=" + dsize + ", src=" + ssize + "]."); - return; - } - - for (int ii = 0; ii < dsize; ii++) { - Image dimg = dest.getFrame(ii); - Image simg = src.getFrame(ii); - dimg.getGraphics().drawImage(simg, 0, 0, null); - } - } +// /** +// * Returns the file path for the given action sequence and component. +// */ +// protected static String getImageFile ( +// String imagedir, ActionSequence as, CharacterComponent c) +// { +// return imagedir + as.fileid + "_" + c.getFileId() + IMAGE_SUFFIX; +// } /** * An implementation of the {@link MultiFrameImage} interface that @@ -147,31 +127,35 @@ public class TileUtil protected static class BlankFrameImage implements MultiFrameImage { /** - * Constructs a blank frame image. + * Constructs a blank frame image based on the dimensions of + * template multi-frame image. */ - public BlankFrameImage (int width, int height, int frameCount) + public BlankFrameImage (MultiFrameImage template) { - _imgs = new Image[frameCount]; - for (int ii = 0; ii < frameCount; ii++) { - _imgs[ii] = new BufferedImage( - width, height, BufferedImage.TYPE_INT_ARGB); + int frameCount = template.getFrameCount(); + _images = new Image[frameCount]; + for (int i = 0; i < frameCount; i++) { + Image img = template.getFrame(i); + _images[i] = new BufferedImage(img.getWidth(null), + img.getHeight(null), + BufferedImage.TYPE_INT_ARGB); } } // documentation inherited public int getFrameCount () { - return _imgs.length; + return _images.length; } // documentation inherited public Image getFrame (int index) { - return _imgs[index]; + return _images[index]; } /** The frame images. */ - protected Image _imgs[]; + protected Image[] _images; } /** The image file name suffix appended to component image file names. */ diff --git a/src/java/com/threerings/resource/ResourceManager.java b/src/java/com/threerings/resource/ResourceManager.java index c0adf9f84..0619feeb4 100644 --- a/src/java/com/threerings/resource/ResourceManager.java +++ b/src/java/com/threerings/resource/ResourceManager.java @@ -1,5 +1,5 @@ // -// $Id: ResourceManager.java,v 1.5 2001/11/26 23:48:04 mdb Exp $ +// $Id: ResourceManager.java,v 1.6 2001/11/27 08:09:35 mdb Exp $ package com.threerings.resource; @@ -186,7 +186,7 @@ public class ResourceManager while (tok.hasMoreTokens()) { // obtain the path and fully qualify it - String path = tok.nextToken(); + String path = tok.nextToken().trim(); if (!path.startsWith(File.separator)) { path = appRoot + path; }