diff --git a/src/as/com/threerings/cast/ActionFrames.as b/src/as/com/threerings/cast/ActionFrames.as new file mode 100644 index 00000000..a25c3ec6 --- /dev/null +++ b/src/as/com/threerings/cast/ActionFrames.as @@ -0,0 +1,71 @@ +// +// Nenya library - tools for developing networked games +// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/nenya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.cast { + +import flash.display.DisplayObject; + +/** + * Encapsulates a set of frames in each of {@link + * DirectionCodes#DIRECTION_COUNT} orientations that are used to render a + * character sprite. + */ +public interface ActionFrames +{ + /** + * Returns the number of orientations available in this set of action + * frames. + */ + function getOrientationCount () :int; + + /** + * Returns the multi-frame image that comprises the frames for the + * specified orientation. + */ + function getFrames (orient :int) :DisplayObject; + + /** + * Returns the x offset from the upper left of the image to the + * "origin" for this character frame. A sprite with location (x, y) + * will be rendered such that its origin is coincident with that + * location. + */ + function getXOrigin (orient :int, frameIdx :int) :int; + + /** + * Returns the y offset from the upper left of the image to the + * "origin" for this character frame. A sprite with location (x, y) + * will be rendered such that its origin is coincident with that + * location. + */ + function getYOrigin (orient :int, frameIdx :int) :int; + + /** + * Creates a clone of these action frames which will have the supplied + * colorizations applied to the frame images. + */ + function cloneColorized (zations :Array) :ActionFrames; + + /** + * Creates a clone of these action frames which will have the supplied + * translation applied to the frame images. + */ + function cloneTranslated (dx :int, dy :int) :ActionFrames; +} +} \ No newline at end of file diff --git a/src/as/com/threerings/cast/CharacterComponent.as b/src/as/com/threerings/cast/CharacterComponent.as new file mode 100644 index 00000000..9a413473 --- /dev/null +++ b/src/as/com/threerings/cast/CharacterComponent.as @@ -0,0 +1,113 @@ +// +// Nenya library - tools for developing networked games +// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/nenya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.cast { + +import com.threerings.util.Hashable; +import com.threerings.util.Equalable; +import com.threerings.util.Set; +import com.threerings.util.StringUtil; + +/** + * The character component represents a single component that can be 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 + implements Equalable, Hashable +{ + /** The unique component identifier. */ + public var componentId :int; + + /** The component's name. */ + public var name :String; + + /** The class of components to which this one belongs. */ + public var componentClass :ComponentClass; + + /** + * Constructs a character component with the specified id of the specified class. + */ + public function CharacterComponent ( + componentId :int, name :String, compClass :ComponentClass, fprov :FrameProvider) + { + this.componentId = componentId; + this.name = name; + this.componentClass = compClass; + _frameProvider = fprov; + } + + /** + * Returns the render priority appropriate for this component at the specified action and + * orientation. + */ + public function getRenderPriority (action :String, orientation :int) :int + { + return componentClass.getRenderPriority(action, name, orientation); + } + + /** + * Returns the image frames for the specified action animation or null if no animation for the + * specified action is available for this component. + * + * @param type null for the normal action frames or one of the custom action sub-types: + * {@link StandardActions#SHADOW_TYPE}, etc. + */ + public function getFrames (action :String, type :String) :ActionFrames + { + return _frameProvider.getFrames(this, action, type); + } + + /** + * Returns the path to the image frames for the specified action animation or null if no + * animation for the specified action is available for this component. + * + * @param type null for the normal action frames or one of the custom action sub-types: + * {@link StandardActions#SHADOW_TYPE}, etc. + * + * @param existentPaths the set of all paths for which there are valid frames. + */ + public function getFramePath (action :String, type :String, existentPaths :Set) :String + { + return _frameProvider.getFramePath(this, action, type, existentPaths); + } + + public function equals (other :Object) :Boolean + { + if (other is CharacterComponent) { + return componentId == (CharacterComponent(other)).componentId; + } else { + return false; + } + } + + public function hashCode () :int + { + return componentId; + } + + public function toString () :String + { + return StringUtil.toString(this); + } + + /** The entity from which we obtain our animation frames. */ + protected var _frameProvider :FrameProvider; +} +} \ No newline at end of file diff --git a/src/as/com/threerings/cast/CharacterDescriptor.as b/src/as/com/threerings/cast/CharacterDescriptor.as new file mode 100644 index 00000000..f6332a8d --- /dev/null +++ b/src/as/com/threerings/cast/CharacterDescriptor.as @@ -0,0 +1,158 @@ +// +// Nenya library - tools for developing networked games +// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/nenya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.cast { + +import com.threerings.media.image.Colorization; +import com.threerings.util.ArrayUtil; +import com.threerings.util.Equalable; +import com.threerings.util.Hashable; +import com.threerings.util.StringUtil; + +/** + * The character descriptor object details the components that are + * pieced together to create a single character image. + */ +public class CharacterDescriptor + implements Equalable, Hashable +{ + /** + * Constructs a character descriptor. + * + * @param components the component ids of the individual components + * that make up this character. + * @param zations the colorizations to apply to each of the character + * component images when compositing actions (an array of + * colorizations for each component, elements of which can be null to + * make it easier to support per-component specialized colorizations). + * This can be null if image recolorization is not desired. + */ + public function CharacterDescriptor (components :Array, zations :Array) + { + _components = components; + _zations = zations; + } + + /** + * Returns an array of the component identifiers comprising the + * character described by this descriptor. + */ + public function getComponentIds () :Array + { + return _components; + } + + /** + * Returns an array of colorization arrays to be applied to the + * components when compositing action images (one array per + * component). + */ + public function getColorizations () :Array + { + return _zations; + } + + /** + * Updates the colorizations to be used by this character descriptor. + */ + public function setColorizations (zations :Array) :void + { + _zations = zations; + } + + /** + * Returns the array of translations to be applied to the components + * when compositing action images. + */ + public function getTranslations () :Array + { + return _xlations; + } + + /** + * Updates the translations to be used by this character descriptor. + */ + public function setTranslations (xlations :Array) :void + { + _xlations = xlations; + } + + public function hashCode () :int + { + var code :int = 0; + var clength :int = _components.length; + for (var ii :int = 0; ii < clength; ii++) { + code ^= _components[ii]; + } + return code; + } + + public function equals (other :Object) :Boolean + { + if (!(other is CharacterDescriptor)) { + return false; + } + + // both the component ids and the colorizations must be equal + var odesc :CharacterDescriptor = CharacterDescriptor(other); + if (!ArrayUtil.equals(_components, odesc._components)) { + return false; + } + + var zations :Array = odesc._zations; + if (zations == null && _zations == null) { + // if neither has colorizations, we're clear + return true; + + } else if (zations == null || _zations == null) { + // if one has colorizations whilst the other doesn't, they + // can't be equal + return false; + } + + // otherwise, all of the colorizations must be equal as well + var zlength :int= zations.length; + if (zlength != _zations.length) { + return false; + } + for (var ii :int= 0; ii < zlength; ii++) { + if (!ArrayUtil.equals(_zations[ii], zations[ii])) { + return false; + } + } + + return ArrayUtil.equals(_xlations, odesc._xlations); + } + + public function toString () :String + { + return "[cids=" + StringUtil.toString(_components) + + ", colors=" + StringUtil.toString(_zations) + "]"; + } + + /** The component identifiers comprising the character. */ + protected var _components :Array; + + /** The colorizations to apply when compositing this character. */ + protected var _zations :Array; + + /** The translations to apply when compositing this character. */ + protected var _xlations :Array; +} +} \ No newline at end of file diff --git a/src/as/com/threerings/cast/ComponentClass.as b/src/as/com/threerings/cast/ComponentClass.as new file mode 100644 index 00000000..064ddd0e --- /dev/null +++ b/src/as/com/threerings/cast/ComponentClass.as @@ -0,0 +1,196 @@ +// +// Nenya library - tools for developing networked games +// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/nenya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.cast { + +import com.threerings.util.ArrayUtil; +import com.threerings.util.Hashable; +import com.threerings.util.Equalable; +import com.threerings.util.StringUtil; + +/** + * 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. + * + *

Components support render priority overrides for particular + * actions, orientations or combinations of actions and orientations. The + * system is currently structured with the expectation that the overrides + * will be relatively few (less than fifteen, say) for any given component + * class. A system that relied on many overrides for its components would + * want to implement a more scalable algorithm for determining which, if + * any, override matches a particular action and orientation combination. + */ +public class ComponentClass + implements Equalable, Hashable +{ + /** The component class name. */ + public var name :String; + + /** The default render priority. */ + public var renderPriority :int; + + /** The color classes to use when recoloring components of this class. May + * be null if a system does not use recolorable components. */ + public var colors :Array; + + /** The class name of the layer from which this component class obtains a + * mask to limit rendering to certain areas. */ + public var mask :String; + + /** Indicates the class name of the shadow layer to which this component + * class contributes a shadow. */ + public var shadow :String; + + /** 1.0 for a normal component, the alpha value of the pre-composited + * shadow for the special "shadow" component class. */ + public var shadowAlpha :Number = 1.0; + + /** Whether or not components of this class will have translations applied. */ + public var translate :Boolean; + + /** + * Creates an uninitialized instance suitable for unserialization or + * population during XML parsing. + */ + public function ComponentClass () + { + } + + public static function fromXml (xml :XML) :ComponentClass + { + var compClass :ComponentClass = new ComponentClass(); + compClass.name = xml.@name; + compClass.renderPriority = xml.@renderPriority; + compClass.colors = toStrArray(xml.@colors); + compClass.mask = xml.@mask; + compClass.shadow = xml.@shadow; + compClass.shadowAlpha = xml.@shadowAlpha; + compClass.translate = xml.@translate; + for each (var overrideXml :XML in xml.override) { + compClass.addPriorityOverride(PriorityOverride.fromXml(overrideXml)); + } + return compClass; + } + + protected static function toStrArray (str :String) :Array + { + if (str == null || str.length == 0) { + return null; + } + + return str.split(",").map(function(element :String, index :int, arr :Array) :String { + return StringUtil.trim(element); + }); + } + + /** + * Returns the render priority appropriate for the specified action, orientation and + * component. + */ + public function getRenderPriority (action :String, component :String, orientation :int) :int + { + // because we expect there to be relatively few priority overrides, we simply search + // linearly through the list for the closest match + var ocount :int = (_overrides != null) ? _overrides.size() : 0; + for (var ii :int = 0; ii < ocount; ii++) { + var over :PriorityOverride = _overrides.get(ii); + // based on the way the overrides are sorted, the first match + // is the most specific and the one we want + if (over.matches(action, component, orientation)) { + return over.renderPriority; + } + } + + return renderPriority; + } + + /** + * Adds the supplied render priority override record to this component class. + */ + public function addPriorityOverride (override :PriorityOverride) :void + { + if (_overrides == null) { + _overrides = []; + } + ArrayUtil.sortedInsert(_overrides, override); + } + + /** + * Returns true if this component class contributes a shadow to a + * particular shadow layer. Note: this is different from being a + * shadow layer which is determined by calling {@link #isShadow}. + */ + public function isShadowed () :Boolean + { + return (shadow != null); + } + + /** + * Returns true if this component class is a shadow layer rather than a normal component class. + */ + public function isShadow () :Boolean + { + return (shadowAlpha != 1.0); + } + + /** + * Classes with the same name are the same. + */ + public function equals (other :Object) :Boolean + { + if (other is ComponentClass) { + return name == (ComponentClass(other)).name; + } else { + return false; + } + } + + /** + * Hashcode is based on component class name. + */ + public function hashCode () :int + { + return StringUtil.hashCode(name); + } + + public function toString () :String + { + var buf :String = "["; + buf = buf.concat("name=", name); + buf = buf.concat(", pri=", renderPriority); + if (colors != null) { + buf = buf.concat(", colors=", StringUtil.toString(colors)); + } + if (mask != null) { + buf = buf.concat(", mask=", mask); + } + if (shadowAlpha != 1.0) { + buf = buf.concat(", shadow=", shadowAlpha); + } else if (shadow != null) { + buf = buf.concat(", shadow=", shadow); + } + return buf.concat("]"); + } + + /** A list of render priority overrides. */ + protected var _overrides :Array = []; +} +} \ No newline at end of file diff --git a/src/as/com/threerings/cast/FrameProvider.as b/src/as/com/threerings/cast/FrameProvider.as new file mode 100644 index 00000000..1daefc61 --- /dev/null +++ b/src/as/com/threerings/cast/FrameProvider.as @@ -0,0 +1,48 @@ +// +// Nenya library - tools for developing networked games +// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/nenya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.cast { + +import com.threerings.util.Set; + +/** + * 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. + */ + function getFrames ( + component :CharacterComponent, action :String, type :String) :ActionFrames; + + /** + * Returns the file path of the animation frames (in the eight sprite directions) for the + * specified action of the specified component. May return a path to the default action or + * null if the specified action does not exist for the specified component. + * + * @param existentPaths the set of all paths for which there are valid frames. + */ + function getFramePath ( + component :CharacterComponent, action :String, type :String, existentPaths :Set):String; +} +} \ No newline at end of file diff --git a/src/as/com/threerings/cast/PriorityOverride.as b/src/as/com/threerings/cast/PriorityOverride.as new file mode 100644 index 00000000..db273b45 --- /dev/null +++ b/src/as/com/threerings/cast/PriorityOverride.as @@ -0,0 +1,133 @@ +// +// Nenya library - tools for developing networked games +// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/nenya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.cast { + +import com.threerings.util.Comparable; +import com.threerings.util.DirectionUtil; +import com.threerings.util.Hashable; +import com.threerings.util.Equalable; +import com.threerings.util.Set; +import com.threerings.util.Sets; +import com.threerings.util.StringUtil; + +/** Used to effect custom render orders for particular actions, orientations, etc. */ +public class PriorityOverride + implements Comparable, Hashable, Equalable +{ + /** The overridden render priority value. */ + public var renderPriority :int; + + /** The action, if any, for which this override is appropriate. */ + public var action :String; + + /** The component, if any, for which this override is appropriate. */ + public var component :String; + + /** The orientations, if any, for which this override is appropriate. */ + public var orients :Set; + + public static function fromXml (xml :XML) :PriorityOverride + { + var override :PriorityOverride = new PriorityOverride; + override.renderPriority = xml.@renderPriority; + override.action = xml.@action; + override.component = xml.@component; + override.orients = Sets.newSetOf(int); + for each (var orient :int in toOrientArray(xml.@orients)) { + override.orients.add(orient); + } + return override; + } + + protected static function toOrientArray (str :String) :Array + { + if (str == null || str.length == 0) { + return null; + } + + return str.split(",").map(function(element :String, index :int, arr :Array) :int { + return DirectionUtil.fromShortString(StringUtil.trim(element)); + }); + } + + /** + * Determines whether this priority override matches the specified + * action, orientation ant component combination. + */ + public function matches (action :String, component :String, orient :int) :Boolean + { + return (((orients == null) || orients.contains(orient)) && + ((this.component == null) || this.component == component) && + ((this.action == null) || this.action == action)); + } + + // documentation inherited from interface + public function compareTo (po :Object) :int + { + + // overrides with both an action and an orientation should come first in the list + var pri :int = priority(); + var opri :int = PriorityOverride(po).priority(); + if (pri == opri) { + return hashCode() - PriorityOverride(po).hashCode(); + } else { + return pri - opri; + } + } + + public function hashCode () :int + { + return StringUtil.hashCode(action) ^ StringUtil.hashCode(component) ^ renderPriority; + } + + public function equals (other :Object) :Boolean + { + if (!(other is PriorityOverride)) { + return false; + } + var otherOverride :PriorityOverride = PriorityOverride(other); + + return otherOverride.renderPriority == renderPriority && otherOverride.action == action && + otherOverride.component == component; + } + + protected function priority () :int + { + var priority :int = 0; + if (component != null) { + priority += 3; // Extra priority to things that are for specific components + } + if (action != null) { + priority++; + } + if (orients != null) { + priority++; + } + return priority; + } + + public function toString () :String + { + return "[pri=" + renderPriority + ", action=" + action + ", component=" + component + + ", orients=" + orients + "]"; + } + +} +} \ No newline at end of file