Split Nenya into proper Maven submodules (same treatment Narya got).
This commit is contained in:
+110
@@ -0,0 +1,110 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.threerings</groupId>
|
||||
<artifactId>nenya-parent</artifactId>
|
||||
<version>1.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>nenyalib</artifactId>
|
||||
<packaging>swc</packaging>
|
||||
<name>Nenya ActionScript</name>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>flexlib-repo</id>
|
||||
<url>http://maven.ow2.org/maven2</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>ooo-ext-maven-repo</id>
|
||||
<url>http://ooo-maven.googlecode.com/hg/repository</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>ooo-maven-repo</id>
|
||||
<url>http://threerings.github.com/maven-repo</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<properties>
|
||||
<flex.version>4.1.0.16076</flex.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.threerings</groupId>
|
||||
<artifactId>aspirin</artifactId>
|
||||
<version>1.9</version>
|
||||
<type>swc</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>flexlib</groupId>
|
||||
<artifactId>flexlib-bin</artifactId>
|
||||
<version>2.4</version>
|
||||
<type>swc</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>as3isolib</groupId>
|
||||
<artifactId>as3isolib-fp9</artifactId>
|
||||
<version>r298</version>
|
||||
<type>swc</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.threerings</groupId>
|
||||
<artifactId>naryalib</artifactId>
|
||||
<version>1.11</version>
|
||||
<type>swc</type>
|
||||
</dependency>
|
||||
<!-- needed for the build, but not an exported dependency -->
|
||||
<dependency>
|
||||
<groupId>com.adobe.flex.framework</groupId>
|
||||
<artifactId>flex-framework</artifactId>
|
||||
<version>${flex.version}</version>
|
||||
<type>pom</type>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>flexmojos</id>
|
||||
<url>http://repository.sonatype.org/content/groups/flexgroup/</url>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
|
||||
<build>
|
||||
<sourceDirectory>src/main/as</sourceDirectory>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.sonatype.flexmojos</groupId>
|
||||
<artifactId>flexmojos-maven-plugin</artifactId>
|
||||
<extensions>true</extensions>
|
||||
<version>4.2-beta</version>
|
||||
<configuration>
|
||||
<omitTraceStatements>false</omitTraceStatements>
|
||||
<debug>true</debug>
|
||||
<incremental>false</incremental>
|
||||
<useNetwork>false</useNetwork>
|
||||
<verboseStacktraces>true</verboseStacktraces>
|
||||
<!-- we've never shown them in the Ant build, why start now? -->
|
||||
<showWarnings>false</showWarnings>
|
||||
</configuration>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.adobe.flex</groupId>
|
||||
<artifactId>compiler</artifactId>
|
||||
<version>${flex.version}</version>
|
||||
<type>pom</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.adobe.flex.compiler</groupId>
|
||||
<artifactId>asdoc</artifactId>
|
||||
<version>${flex.version}</version>
|
||||
<classifier>template</classifier>
|
||||
<type>zip</type>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,73 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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, callback :Function) :void;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.cast {
|
||||
|
||||
import flash.geom.Point;
|
||||
|
||||
import com.threerings.media.tile.SwissArmyTileSet;
|
||||
import com.threerings.util.DirectionUtil;
|
||||
import com.threerings.util.StringUtil;
|
||||
|
||||
/**
|
||||
* The action sequence class describes a particular character animation
|
||||
* sequence. An animation sequence consists of one or more frames of
|
||||
* animation, renders at a particular frame rate, and has an origin point
|
||||
* that specifies the location of the base of the character in relation to
|
||||
* the bounds of the animation images.
|
||||
*/
|
||||
public class ActionSequence
|
||||
{
|
||||
/**
|
||||
* Defines the name of the default action sequence. When component
|
||||
* tilesets are loaded to build a set of composited images for a
|
||||
* particular action sequence, a check is first made for a component
|
||||
* tileset specific to the action sequence and then for the
|
||||
* component's default tileset if the action specific tileset did not
|
||||
* exist.
|
||||
*/
|
||||
public static const DEFAULT_SEQUENCE :String = "default";
|
||||
|
||||
/** The action sequence name. */
|
||||
public var name :String;
|
||||
|
||||
/** The number of frames per second to show when animating. */
|
||||
public var framesPerSecond :Number;
|
||||
|
||||
/** The position of the character's base for this sequence. */
|
||||
public var origin :Point = new Point();
|
||||
|
||||
public var tileset :SwissArmyTileSet;
|
||||
|
||||
/** Orientation codes for the orientations available for this
|
||||
* action. */
|
||||
public var orients :Array;
|
||||
|
||||
public static function fromXml (xml :XML) :ActionSequence
|
||||
{
|
||||
var seq :ActionSequence = new ActionSequence();
|
||||
seq.name = xml.@name;
|
||||
seq.framesPerSecond = xml.framesPerSecond;
|
||||
seq.orients = toOrientArray(xml.orients);
|
||||
seq.origin = toPoint(xml.origin);
|
||||
seq.tileset = SwissArmyTileSet.fromXml(xml.tileset[0]);
|
||||
|
||||
return seq;
|
||||
}
|
||||
|
||||
protected static function toOrientArray (str :String) :Array
|
||||
{
|
||||
if (str == null || str.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return str.split(",").map(function(element :String, index :int, arr :Array) :int {
|
||||
return DirectionUtil.fromShortString(StringUtil.trim(element));
|
||||
});
|
||||
}
|
||||
|
||||
protected static function toPoint (str :String) :Point
|
||||
{
|
||||
if (str == null || str.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var coords :Array =
|
||||
str.split(",").map(function(element :String, index :int, arr :Array) :int {
|
||||
return int(StringUtil.trim(element));
|
||||
});
|
||||
|
||||
return new Point(coords[0], coords[1]);
|
||||
}
|
||||
|
||||
public function toString () :String
|
||||
{
|
||||
return "[name=" + name + ", framesPerSecond=" + framesPerSecond +
|
||||
", origin=" + origin +
|
||||
", orients=" + (orients == null ? 0 : orients.length) + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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;
|
||||
}
|
||||
|
||||
public function setFrameProvider (frameProvider :FrameProvider) :void
|
||||
{
|
||||
_frameProvider = frameProvider;
|
||||
|
||||
// Notify them all and clear our list.
|
||||
for each (var func :Function in _notifyOnLoad) {
|
||||
func(this);
|
||||
}
|
||||
_notifyOnLoad = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the render priority appropriate for this component at the specified action and
|
||||
* orientation.
|
||||
*/
|
||||
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 isLoaded() ? _frameProvider.getFrames(this, action, type) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.simpleToString(this);
|
||||
}
|
||||
|
||||
public function isLoaded () :Boolean
|
||||
{
|
||||
return _frameProvider != null;
|
||||
}
|
||||
|
||||
public function notifyOnLoad (func :Function) :void
|
||||
{
|
||||
if (isLoaded()) {
|
||||
func(this);
|
||||
} else {
|
||||
_notifyOnLoad.push(func);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** The entity from which we obtain our animation frames. */
|
||||
protected var _frameProvider :FrameProvider;
|
||||
|
||||
/** Everyone who cares when we're loaded. */
|
||||
protected var _notifyOnLoad :Array = [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.Arrays;
|
||||
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 (!Arrays.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 (!Arrays.equals(_zations[ii], zations[ii])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return Arrays.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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.cast {
|
||||
|
||||
import flash.geom.Point;
|
||||
|
||||
import com.threerings.util.Hashable;
|
||||
import com.threerings.util.Log;
|
||||
import com.threerings.util.Map;
|
||||
import com.threerings.util.StringUtil;
|
||||
import com.threerings.util.Maps;
|
||||
import com.threerings.util.maps.LRMap;
|
||||
|
||||
/**
|
||||
* The character manager provides facilities for constructing sprites that
|
||||
* are used to represent characters in a scene. It also handles the
|
||||
* compositing and caching of composited character animations.
|
||||
*/
|
||||
public class CharacterManager
|
||||
{
|
||||
private static var log :Log = Log.getLog(CharacterManager);
|
||||
|
||||
/**
|
||||
* Constructs the character manager.
|
||||
*/
|
||||
public function CharacterManager (crepo :ComponentRepository)
|
||||
{
|
||||
// keep this around
|
||||
_crepo = crepo;
|
||||
|
||||
for each (var action :ActionSequence in crepo.getActionSequences()) {
|
||||
_actions.put(action.name, action);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link CharacterSprite} representing the character
|
||||
* described by the given {@link CharacterDescriptor}, or
|
||||
* <code>null</code> if an error occurs.
|
||||
*
|
||||
* @param desc the character descriptor.
|
||||
*/
|
||||
public function getCharacter (desc :CharacterDescriptor,
|
||||
charClass :Class = null) :CharacterSprite
|
||||
{
|
||||
if (charClass == null) {
|
||||
charClass = _charClass;
|
||||
}
|
||||
|
||||
try {
|
||||
var sprite :CharacterSprite = new charClass();
|
||||
sprite.init(desc, this);
|
||||
return sprite;
|
||||
|
||||
} catch (e :Error) {
|
||||
log.warning("Failed to instantiate character sprite.", e);
|
||||
return null;
|
||||
}
|
||||
return getCharacter(desc, charClass);
|
||||
}
|
||||
|
||||
public function getActionSequence (action :String) :ActionSequence
|
||||
{
|
||||
return _actions.get(action);
|
||||
}
|
||||
|
||||
public function getComponent (compId :int) :CharacterComponent
|
||||
{
|
||||
return _crepo.getComponent(compId);
|
||||
}
|
||||
|
||||
public function getComponentByName (cclass :String, cname :String) :CharacterComponent
|
||||
{
|
||||
return _crepo.getComponentByName(cclass, cname);
|
||||
}
|
||||
|
||||
public function getActionFrames (descrip :CharacterDescriptor, action :String) :ActionFrames
|
||||
{
|
||||
if (!isLoaded(descrip)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var key :FrameKey = new FrameKey(descrip, action);
|
||||
|
||||
var frames :ActionFrames = _actionFrames.get(key);
|
||||
if (frames == null) {
|
||||
// this doesn't actually composite the images, but prepares an
|
||||
// object to be able to do so
|
||||
frames = createCompositeFrames(descrip, action);
|
||||
_actionFrames.put(key, frames);
|
||||
}
|
||||
|
||||
return frames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether all the components are loaded and ready to go.
|
||||
*/
|
||||
protected function isLoaded (descrip :CharacterDescriptor) :Boolean
|
||||
{
|
||||
var cids :Array = descrip.getComponentIds();
|
||||
var ccount :int = cids.length;
|
||||
|
||||
for (var ii :int = 0; ii < ccount; ii++) {
|
||||
var ccomp :CharacterComponent = _crepo.getComponent(cids[ii]);
|
||||
if (!ccomp.isLoaded()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function load (descrip :CharacterDescriptor, notify :Function) :void
|
||||
{
|
||||
_crepo.load(descrip.getComponentIds(), notify);
|
||||
}
|
||||
|
||||
protected function createCompositeFrames (descrip :CharacterDescriptor,
|
||||
action :String) :ActionFrames
|
||||
{
|
||||
var cids :Array = descrip.getComponentIds();
|
||||
var ccount :int = cids.length;
|
||||
|
||||
var zations :Array = descrip.getColorizations();
|
||||
var xlations :Array = descrip.getTranslations();
|
||||
|
||||
log.debug("Compositing action [action=" + action +
|
||||
", descrip=" + descrip + "].");
|
||||
|
||||
// this will be used to construct any shadow layers
|
||||
var shadows :Map = null; /* of String, Array<TranslatedComponent> */
|
||||
|
||||
// maps components by class name for masks
|
||||
var ccomps :Map = Maps.newMapOf(String); /* of String, ArrayList<TranslatedComponent> */
|
||||
|
||||
// create colorized versions of all of the source action frames
|
||||
var sources :Array = [];
|
||||
for (var ii :int = 0; ii < ccount; ii++) {
|
||||
var cframes :ComponentFrames = new ComponentFrames();
|
||||
sources.push(cframes);
|
||||
var ccomp :CharacterComponent = cframes.ccomp = _crepo.getComponent(cids[ii]);
|
||||
|
||||
// load up the main component images
|
||||
var source :ActionFrames = ccomp.getFrames(action, null);
|
||||
if (source == null) {
|
||||
var errmsg :String = "Cannot composite action frames; no such " +
|
||||
"action for component [action=" + action +
|
||||
", desc=" + descrip + ", comp=" + ccomp + "]";
|
||||
throw new Error(errmsg);
|
||||
}
|
||||
source = (zations == null || zations[ii] == null) ?
|
||||
source : source.cloneColorized(zations[ii]);
|
||||
var xlation :Point = (xlations == null) ? null : xlations[ii];
|
||||
cframes.frames = (xlation == null) ?
|
||||
source : source.cloneTranslated(xlation.x, xlation.y);
|
||||
|
||||
// store the component with its translation under its class for masking
|
||||
var tcomp :TranslatedComponent = new TranslatedComponent(ccomp, xlation);
|
||||
var tcomps :Array = ccomps.get(ccomp.componentClass.name);
|
||||
if (tcomps == null) {
|
||||
ccomps.put(ccomp.componentClass.name, tcomps = []);
|
||||
}
|
||||
tcomps.push(tcomp);
|
||||
|
||||
// if this component has a shadow, make a note of it
|
||||
if (ccomp.componentClass.isShadowed()) {
|
||||
if (shadows == null) {
|
||||
shadows = Maps.newMapOf(String);
|
||||
}
|
||||
var shadlist :Array = shadows.get(ccomp.componentClass.shadow);
|
||||
if (shadlist == null) {
|
||||
shadows.put(ccomp.componentClass.shadow, shadlist = []);
|
||||
}
|
||||
shadlist.push(tcomp);
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO - Implement shadows & masks - I don't actually know if/where we use these, though.
|
||||
// now create any necessary shadow layers
|
||||
if (shadows != null) {
|
||||
for (Map.Entry<String, ArrayList<TranslatedComponent>> entry : shadows.entrySet()) {
|
||||
ComponentFrames scf = compositeShadow(action, entry.getKey(), entry.getValue());
|
||||
if (scf != null) {
|
||||
sources.add(scf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add any necessary masks
|
||||
for (ComponentFrames cframes : sources) {
|
||||
ArrayList<TranslatedComponent> mcomps = ccomps.get(cframes.ccomp.componentClass.mask);
|
||||
if (mcomps != null) {
|
||||
cframes.frames = compositeMask(action, cframes.ccomp, cframes.frames, mcomps);
|
||||
}
|
||||
}
|
||||
*/
|
||||
// use those to create an entity that will lazily composite things
|
||||
// together as they are needed
|
||||
return new CompositedActionFrames(_frameCache, _actions.get(action), sources);
|
||||
}
|
||||
|
||||
protected var _crepo :ComponentRepository;
|
||||
|
||||
protected var _actions :Map = Maps.newMapOf(String);
|
||||
|
||||
protected var _actionFrames :Map = Maps.newMapOf(FrameKey);
|
||||
|
||||
protected var _frameCache :LRMap = new LRMap(Maps.newMapOf(CompositedFramesKey), MAX_FRAMES);
|
||||
|
||||
protected var _charClass :Class;
|
||||
|
||||
protected static const MAX_FRAMES :int = 1000;
|
||||
}
|
||||
}
|
||||
|
||||
import com.threerings.util.Hashable;
|
||||
import com.threerings.util.StringUtil;
|
||||
import com.threerings.cast.CharacterDescriptor;
|
||||
|
||||
class FrameKey
|
||||
implements Hashable
|
||||
{
|
||||
public var desc :CharacterDescriptor;
|
||||
|
||||
public var action :String;
|
||||
|
||||
public function FrameKey (desc :CharacterDescriptor, action :String)
|
||||
{
|
||||
this.desc = desc;
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
public function hashCode () :int
|
||||
{
|
||||
return desc.hashCode() ^ StringUtil.hashCode(action);
|
||||
}
|
||||
|
||||
public function equals (other :Object) :Boolean
|
||||
{
|
||||
if (other is FrameKey) {
|
||||
var okey :FrameKey = FrameKey(other);
|
||||
return okey.desc.equals(desc) && okey.action == action;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
import flash.geom.Point;
|
||||
|
||||
import com.threerings.cast.ActionFrames;
|
||||
import com.threerings.cast.CharacterComponent;
|
||||
|
||||
class TranslatedComponent
|
||||
{
|
||||
public var ccomp :CharacterComponent;
|
||||
public var xlation :Point;
|
||||
|
||||
public function TranslatedComponent (ccomp :CharacterComponent, xlation :Point)
|
||||
{
|
||||
this.ccomp = ccomp;
|
||||
this.xlation = xlation;
|
||||
}
|
||||
|
||||
public function getFrames (action :String, type :String) :ActionFrames
|
||||
{
|
||||
var frames :ActionFrames = ccomp.getFrames(action, type);
|
||||
return (frames == null || xlation == null) ?
|
||||
frames : frames.cloneTranslated(xlation.x, xlation.y);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.cast {
|
||||
|
||||
import flash.display.DisplayObject;
|
||||
import flash.display.Sprite;
|
||||
|
||||
import flash.geom.Point;
|
||||
import flash.geom.Rectangle;
|
||||
|
||||
import com.threerings.display.DisplayUtil;
|
||||
import com.threerings.media.Tickable;
|
||||
import com.threerings.util.DirectionCodes;
|
||||
import com.threerings.util.Log;
|
||||
|
||||
/**
|
||||
* A character sprite is a sprite that animates itself while walking
|
||||
* about in a scene.
|
||||
*/
|
||||
public class CharacterSprite extends Sprite
|
||||
implements Tickable
|
||||
{
|
||||
private static var log :Log = Log.getLog(CharacterSprite);
|
||||
|
||||
/**
|
||||
* Initializes this character sprite with the specified character
|
||||
* descriptor and character manager. It will obtain animation data
|
||||
* from the supplied character manager.
|
||||
*/
|
||||
public function init (descrip :CharacterDescriptor, charmgr :CharacterManager) :void
|
||||
{
|
||||
// keep track of this stuff
|
||||
_descrip = descrip;
|
||||
_charmgr = charmgr;
|
||||
|
||||
// sanity check our values
|
||||
sanityCheckDescrip();
|
||||
|
||||
// assign an arbitrary starting orientation
|
||||
_orient = DirectionCodes.SOUTHWEST;
|
||||
|
||||
// pass the buck to derived classes
|
||||
didInit();
|
||||
|
||||
halt();
|
||||
|
||||
updateActionFrames();
|
||||
}
|
||||
|
||||
public function tick (tickStamp :int) :void
|
||||
{
|
||||
if (_framesBitmap != null) {
|
||||
_framesBitmap.tick(tickStamp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after this sprite has been initialized with its character
|
||||
* descriptor and character manager. Derived classes can do post-init
|
||||
* business here.
|
||||
*/
|
||||
protected function didInit () :void
|
||||
{
|
||||
_mainSprite = new Sprite();
|
||||
addChild(_mainSprite);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconfigures this sprite to use the specified character descriptor.
|
||||
*/
|
||||
public function setCharacterDescriptor (descrip :CharacterDescriptor) :void
|
||||
{
|
||||
// keep the new descriptor
|
||||
_descrip = descrip;
|
||||
|
||||
// sanity check our values
|
||||
sanityCheckDescrip();
|
||||
|
||||
// update our action frames
|
||||
updateActionFrames();
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the action to use when the sprite is at rest. The default
|
||||
* is <code>STANDING</code>.
|
||||
*/
|
||||
public function setRestingAction (action :String) :void
|
||||
{
|
||||
_restingAction = action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the action to be used when the sprite is at rest. Derived
|
||||
* classes may wish to override this method and vary the action based
|
||||
* on external parameters (or randomly).
|
||||
*/
|
||||
public function getRestingAction () :String
|
||||
{
|
||||
return _restingAction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the action to use when the sprite is following a path.
|
||||
* The default is <code>WALKING</code>.
|
||||
*/
|
||||
public function setFollowingPathAction (action :String) :void
|
||||
{
|
||||
_followingPathAction = action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the action to be used when the sprite is following a path.
|
||||
* Derived classes may wish to override this method and vary the
|
||||
* action based on external parameters (or randomly).
|
||||
*/
|
||||
public function getFollowingPathAction () :String
|
||||
{
|
||||
return _followingPathAction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the action sequence used when rendering the character, from
|
||||
* the set of available sequences.
|
||||
*/
|
||||
public function setActionSequence (action :String) :void
|
||||
{
|
||||
// sanity check
|
||||
if (action == null) {
|
||||
log.warning("Refusing to set null action sequence " + this + ".", new Error());
|
||||
return;
|
||||
}
|
||||
|
||||
// no need to noop
|
||||
if (action == _action) {
|
||||
return;
|
||||
}
|
||||
_action = action;
|
||||
updateActionFrames();
|
||||
}
|
||||
|
||||
public function setOrientation (orient :int) :void
|
||||
{
|
||||
var oorient :int = _orient;
|
||||
_orient = orient;
|
||||
if (orient < 0 || orient >= DirectionCodes.FINE_DIRECTION_COUNT) {
|
||||
log.info("Refusing to set invalid orientation [sprite=" + this +
|
||||
", orient=" + orient + "].", new Error());
|
||||
return;
|
||||
}
|
||||
|
||||
if (_orient != oorient) {
|
||||
updateMainSprite();
|
||||
}
|
||||
}
|
||||
|
||||
public function getOrientation () :int
|
||||
{
|
||||
return _orient;
|
||||
}
|
||||
|
||||
public function cancelMove () :void
|
||||
{
|
||||
halt();
|
||||
}
|
||||
|
||||
public function pathBeginning () :void
|
||||
{
|
||||
// enable walking animation
|
||||
setActionSequence(getFollowingPathAction());
|
||||
}
|
||||
|
||||
public function pathCompleted (timestamp :int) :void
|
||||
{
|
||||
halt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuilds our action frames given our current character descriptor
|
||||
* and action sequence. This is called when either of those two things
|
||||
* changes.
|
||||
*/
|
||||
protected function updateActionFrames () :void
|
||||
{
|
||||
// get a reference to the action sequence so that we can obtain
|
||||
// our animation frames and configure our frames per second
|
||||
var actseq :ActionSequence = _charmgr.getActionSequence(_action);
|
||||
if (actseq == null) {
|
||||
var errmsg :String = "No such action '" + _action + "'.";
|
||||
throw new Error(errmsg);
|
||||
}
|
||||
|
||||
try {
|
||||
// obtain our animation frames for this action sequence
|
||||
_aframes = _charmgr.getActionFrames(_descrip, _action);
|
||||
|
||||
if (_aframes == null) {
|
||||
// Once the frames are really ready to go, we'll do this...
|
||||
_charmgr.load(_descrip, updateActionFrames);
|
||||
}
|
||||
|
||||
updateMainSprite();
|
||||
|
||||
} catch (nsce :NoSuchComponentError) {
|
||||
log.warning("Character sprite references non-existent " +
|
||||
"component [sprite=" + this + ", err=" + nsce + "].");
|
||||
|
||||
} catch (e :Error) {
|
||||
log.warning("Failed to obtain action frames [sprite=" + this +
|
||||
", descrip=" + _descrip + ", action=" + _action + "].", e);
|
||||
}
|
||||
|
||||
x = -actseq.origin.x;
|
||||
y = -actseq.origin.y;
|
||||
}
|
||||
|
||||
protected function updateMainSprite () :void
|
||||
{
|
||||
DisplayUtil.removeAllChildren(_mainSprite);
|
||||
if (_aframes != null) {
|
||||
updateWithUnloadedSprite();
|
||||
var curFrames :ActionFrames = _aframes;
|
||||
_aframes.getFrames(_orient, function(frames :MultiFrameBitmap) :void {
|
||||
// Ensure our action hasn't been swapped out on us since we started loading.
|
||||
if (_aframes == curFrames) {
|
||||
_framesBitmap = frames;
|
||||
DisplayUtil.removeAllChildren(_mainSprite);
|
||||
_mainSprite.addChild(frames);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
_framesBitmap = null;
|
||||
updateWithUnloadedSprite();
|
||||
}
|
||||
}
|
||||
|
||||
protected function updateWithUnloadedSprite () :void
|
||||
{
|
||||
// Nothing by default.
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes it easier to track down problems with bogus character descriptors.
|
||||
*/
|
||||
protected function sanityCheckDescrip () :void
|
||||
{
|
||||
if (_descrip.getComponentIds() == null ||
|
||||
_descrip.getComponentIds().length == 0) {
|
||||
log.warning("Invalid character descriptor [sprite=" + this +
|
||||
", descrip=" + _descrip + "].", new Error());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the sprite animation frame to reflect the cessation of
|
||||
* movement and disables any further animation.
|
||||
*/
|
||||
protected function halt () :void
|
||||
{
|
||||
var rest :String = getRestingAction();
|
||||
if (rest != null) {
|
||||
setActionSequence(rest);
|
||||
}
|
||||
}
|
||||
|
||||
public function hitTest (stageX :int, stageY :int) :Boolean
|
||||
{
|
||||
return _framesBitmap == null ? false : _framesBitmap.hitTest(stageX, stageY);
|
||||
}
|
||||
|
||||
/** The action to use when at rest. */
|
||||
protected var _restingAction :String = StandardActions.STANDING;
|
||||
|
||||
/** The action to use when following a path. */
|
||||
protected var _followingPathAction :String = StandardActions.WALKING;
|
||||
|
||||
/** A reference to the descriptor for the character that we're
|
||||
* visualizing. */
|
||||
protected var _descrip :CharacterDescriptor;
|
||||
|
||||
/** A reference to the character manager that created us. */
|
||||
protected var _charmgr :CharacterManager;
|
||||
|
||||
/** The action we are currently displaying. */
|
||||
protected var _action :String;
|
||||
|
||||
/** The animation frames for the active action sequence in each
|
||||
* orientation. */
|
||||
protected var _aframes :ActionFrames;
|
||||
|
||||
/** The currently active set of bitmaps for this character. */
|
||||
protected var _framesBitmap :MultiFrameBitmap;
|
||||
|
||||
/** The orientation of this sprite. */
|
||||
protected var _orient :int = DirectionCodes.NONE;
|
||||
|
||||
protected var _mainSprite :Sprite;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.Arrays;
|
||||
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.
|
||||
*
|
||||
* <p> 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.length : 0;
|
||||
for (var ii :int = 0; ii < ocount; ii++) {
|
||||
var over :PriorityOverride = _overrides[ii];
|
||||
// based on the way the overrides are sorted, the first match
|
||||
// is the most specific and the one we want
|
||||
if (over.matches(action, component, orientation)) {
|
||||
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 = [];
|
||||
}
|
||||
Arrays.sortedInsert(_overrides, override);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this component class contributes a shadow to a
|
||||
* particular shadow layer. Note: this is different from <em>being</em> 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 = [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.cast {
|
||||
|
||||
import flash.display.Bitmap;
|
||||
import flash.display.BitmapData;
|
||||
import flash.display.DisplayObject;
|
||||
|
||||
import flash.geom.Point;
|
||||
import flash.geom.Rectangle;
|
||||
|
||||
import flash.events.Event;
|
||||
|
||||
import flash.utils.ByteArray;
|
||||
|
||||
import nochump.util.zip.ZipEntry;
|
||||
import nochump.util.zip.ZipError;
|
||||
import nochump.util.zip.ZipFile;
|
||||
|
||||
import com.threerings.media.image.PngRecolorUtil;
|
||||
import com.threerings.media.tile.TileDataPack;
|
||||
import com.threerings.media.tile.TileSet;
|
||||
import com.threerings.util.DataPack;
|
||||
import com.threerings.util.Log;
|
||||
import com.threerings.util.Map;
|
||||
import com.threerings.util.Maps;
|
||||
import com.threerings.util.MultiLoader;
|
||||
import com.threerings.util.Set;
|
||||
import com.threerings.util.StringUtil;
|
||||
|
||||
/**
|
||||
* Like a normal data pack, but we don't deal with any data, and all our filenames are a direct
|
||||
* translation, so we need no metadata file. We also serve as a tile ImageProvider.
|
||||
*/
|
||||
public class ComponentDataPack extends TileDataPack
|
||||
implements FrameProvider
|
||||
{
|
||||
private static var log :Log = Log.getLog(ComponentDataPack);
|
||||
|
||||
public function ComponentDataPack (source :Object, actions :Map,
|
||||
completeListener :Function = null, errorListener :Function = null)
|
||||
{
|
||||
super(source, completeListener, errorListener);
|
||||
_actions = actions;
|
||||
}
|
||||
|
||||
// Documentation inherited from interface
|
||||
public function getFrames (component :CharacterComponent, action :String,
|
||||
type :String) :ActionFrames
|
||||
{
|
||||
// obtain the action sequence definition for this action
|
||||
var actseq :ActionSequence = _actions.get(action);
|
||||
if (actseq == null) {
|
||||
log.warning("Missing action sequence definition [action=" + action +
|
||||
", component=" + component + "].");
|
||||
return null;
|
||||
}
|
||||
|
||||
// determine our image path name
|
||||
var imgpath :String = action;
|
||||
var dimgpath :String = ActionSequence.DEFAULT_SEQUENCE;
|
||||
if (type != null) {
|
||||
imgpath += "_" + type;
|
||||
dimgpath += "_" + type;
|
||||
}
|
||||
|
||||
var root :String = component.componentClass.name + "/" + component.name + "/";
|
||||
var cpath :String = root + imgpath + ".png";
|
||||
var dpath :String = root + dimgpath + ".png";
|
||||
|
||||
// look to see if this tileset is already cached (as the custom action or the default
|
||||
// action)
|
||||
var aset :TileSet = _setcache.get(cpath);
|
||||
if (aset == null) {
|
||||
aset = _setcache.get(dpath);
|
||||
if (aset != null) {
|
||||
// save ourselves a lookup next time
|
||||
_setcache.put(cpath, aset);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// then try loading up a tileset customized for this action
|
||||
if (aset == null) {
|
||||
if (_zip.getEntry(cpath)) {
|
||||
aset = TileSet(actseq.tileset.clone());
|
||||
aset.setImagePath(cpath);
|
||||
} else if (_zip.getEntry(dpath)) {
|
||||
actseq = _actions.get(ActionSequence.DEFAULT_SEQUENCE);
|
||||
aset = TileSet(actseq.tileset.clone());
|
||||
aset.setImagePath(dpath);
|
||||
_setcache.put(dpath, aset);
|
||||
}
|
||||
}
|
||||
|
||||
// if that failed too, we're hosed
|
||||
if (aset == null) {
|
||||
// if this is a shadow or crop image, no need to freak out as they are optional
|
||||
if (!StandardActions.CROP_TYPE == type &&
|
||||
!StandardActions.SHADOW_TYPE == type) {
|
||||
log.warning("Unable to locate tileset for action '" + imgpath + "' " +
|
||||
component + ".");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
aset.setImageProvider(this);
|
||||
_setcache.put(cpath, aset);
|
||||
return new TileSetFrameImage(aset, actseq);
|
||||
|
||||
} catch (e :Error) {
|
||||
log.warning("Error loading tset for action '" + imgpath + "' " + component + ".", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Documentation inherited from interface
|
||||
public function getFramePath (
|
||||
component :CharacterComponent, action :String, type :String, existentPaths :Set) :String
|
||||
{
|
||||
var actionPath :String = makePath(component, action, type);
|
||||
if (!existentPaths.contains(actionPath)) {
|
||||
return makePath(component, ActionSequence.DEFAULT_SEQUENCE, type);
|
||||
}
|
||||
return actionPath;
|
||||
}
|
||||
|
||||
protected function makePath (component :CharacterComponent, action :String,
|
||||
type :String) :String
|
||||
{
|
||||
var imgpath :String = action;
|
||||
if (type != null) {
|
||||
imgpath += "_" + type;
|
||||
}
|
||||
var root :String = component.componentClass.name + "/" + component.name + "/";
|
||||
return root + imgpath + ".png";
|
||||
}
|
||||
|
||||
protected var _actions :Map;
|
||||
|
||||
protected var _setcache :Map = Maps.newMapOf(String);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.cast {
|
||||
|
||||
/** Used to associate a {@link CharacterComponent} with its {@link
|
||||
* ActionFrames} for a particular action. */
|
||||
public class ComponentFrames
|
||||
{
|
||||
public var ccomp :CharacterComponent;
|
||||
|
||||
public var frames :ActionFrames;
|
||||
|
||||
public function ComponentFrames (ccomp :CharacterComponent = null,
|
||||
frames :ActionFrames = null) {
|
||||
this.ccomp = ccomp;
|
||||
this.frames = frames;
|
||||
}
|
||||
|
||||
public function toString () :String {
|
||||
return ccomp + ":" + frames;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.cast {
|
||||
|
||||
/**
|
||||
* Makes available a collection of character components and associated metadata. Character
|
||||
* components are animated sequences that can be composited together to create a complete
|
||||
* character visualization (imagine interchanging pairs of boots, torsos, hats, etc.).
|
||||
*/
|
||||
public interface ComponentRepository
|
||||
{
|
||||
/**
|
||||
* Returns the {@link CharacterComponent} object for the given component identifier.
|
||||
*/
|
||||
function getComponent (componentId :int) :CharacterComponent;
|
||||
|
||||
/**
|
||||
* Returns the {@link CharacterComponent} object with the given component class and name.
|
||||
*/
|
||||
function getComponentByName (className :String, compName :String) :CharacterComponent;
|
||||
|
||||
/**
|
||||
* Returns the {@link ComponentClass} with the specified name or null if none exists with that
|
||||
* name.
|
||||
*/
|
||||
function getComponentClass (className :String) :ComponentClass;
|
||||
|
||||
/**
|
||||
* Iterates over the {@link ComponentClass} instances representing all available character
|
||||
* component classes.
|
||||
*/
|
||||
function getComponentClasses () :Array;
|
||||
|
||||
/**
|
||||
* Iterates over the {@link ActionSequence} instances representing every available action
|
||||
* sequence.
|
||||
*/
|
||||
function getActionSequences () :Array;
|
||||
|
||||
/**
|
||||
* Iterates over the component ids of all components in the specified class.
|
||||
*/
|
||||
function getComponentIds (compClass :ComponentClass) :Array;
|
||||
|
||||
/**
|
||||
* Loads up all the specified components and calls notify() when done.
|
||||
*/
|
||||
function load (compIds :Array, notify :Function) :void;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.cast {
|
||||
|
||||
import flash.display.Bitmap;
|
||||
import flash.display.BitmapData;
|
||||
import flash.display.DisplayObject;
|
||||
|
||||
import flash.geom.Point;
|
||||
import flash.geom.Rectangle;
|
||||
|
||||
import com.threerings.util.Arrays;
|
||||
import com.threerings.util.Hashable;
|
||||
import com.threerings.util.Integer;
|
||||
import com.threerings.util.Map;
|
||||
import com.threerings.util.Maps;
|
||||
import com.threerings.util.RandomUtil;
|
||||
import com.threerings.util.StringUtil;
|
||||
|
||||
/**
|
||||
* An implementation of the {@link ActionFrames} interface that is used
|
||||
* to lazily create composited character frames when they are requested.
|
||||
*/
|
||||
public class CompositedActionFrames
|
||||
implements ActionFrames, Hashable
|
||||
{
|
||||
/**
|
||||
* Constructs a set of composited action frames with the supplied
|
||||
* source frames and colorization configuration. The actual component
|
||||
* frame images will not be composited until they are requested.
|
||||
*/
|
||||
public function CompositedActionFrames (frameCache :Map, action :ActionSequence, sources :Array)
|
||||
{
|
||||
// sanity check
|
||||
if (sources == null || sources.length == 0) {
|
||||
var errmsg :String = "Requested to composite invalid set of source " +
|
||||
"frames! [action=" + action +
|
||||
", sources=" + StringUtil.toString(sources) + "].";
|
||||
throw new Error(errmsg);
|
||||
}
|
||||
|
||||
_frameCache = frameCache;
|
||||
_sources = sources;
|
||||
_action = action;
|
||||
|
||||
// the sources must all have the same orientation count, so we
|
||||
// just use the first
|
||||
|
||||
_orientCount = _sources[0].frames.getOrientationCount();
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public function getOrientationCount () :int
|
||||
{
|
||||
return _orientCount;
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public function getFrames (orient :int, callback :Function) :void
|
||||
{
|
||||
var key :CompositedFramesKey = new CompositedFramesKey(this, orient);
|
||||
var disp :MultiFrameBitmap = _frameCache.get(key);
|
||||
if (disp == null) {
|
||||
var listeners :Array = _pending.get(key);
|
||||
if (listeners != null) {
|
||||
listeners.push(callback);
|
||||
} else {
|
||||
listeners = [callback];
|
||||
_pending.put(key, listeners);
|
||||
|
||||
createFrames(orient, function(disp :MultiFrameBitmap) :void {
|
||||
_frameCache.put(key, disp);
|
||||
for each (var func :Function in listeners) {
|
||||
func(disp.clone());
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
callback(disp.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public function getXOrigin (orient :int, frameIdx :int) :int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public function getYOrigin (orient :int, frameIdx :int) :int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public function cloneColorized (zations :Array) :ActionFrames
|
||||
{
|
||||
throw new Error("Unsupported: CompositedActionFrames.cloneColorized()");
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public function cloneTranslated (dx :int, dy :int) :ActionFrames
|
||||
{
|
||||
var tsources :Array = new Array(_sources.length);
|
||||
for (var ii :int = 0; ii < _sources.length; ii++) {
|
||||
tsources[ii] = new ComponentFrames(
|
||||
_sources[ii].ccomp, _sources[ii].frames.cloneTranslated(dx, dy));
|
||||
}
|
||||
return new CompositedActionFrames(_frameCache, _action, tsources);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates our underlying multi-frame image for a particular orientation.
|
||||
*/
|
||||
protected function createFrames (orient :int, callback :Function) :void
|
||||
{
|
||||
Arrays.stableSort(_sources, function(cf1 :ComponentFrames, cf2 :ComponentFrames) :int {
|
||||
return (cf1.ccomp.getRenderPriority(_action.name, orient) -
|
||||
cf2.ccomp.getRenderPriority(_action.name, orient));
|
||||
});
|
||||
|
||||
var idx :int = Arrays.indexOf(_action.orients, orient);
|
||||
|
||||
var frameCt :int = _action.tileset.getTileCounts()[idx];
|
||||
var width :int = _action.tileset.getWidths()[idx];
|
||||
var height :int = _action.tileset.getHeights()[idx];
|
||||
var frameBitmaps :Array = new Array(frameCt);
|
||||
for (var ff :int = 0; ff < frameCt; ff++) {
|
||||
frameBitmaps[ff] = new Bitmap(new BitmapData(width, height, true, 0x00000000));
|
||||
}
|
||||
|
||||
var region :Rectangle = new Rectangle(0, 0, width, height);
|
||||
var pt :Point = new Point(0, 0);
|
||||
compositeFrames(frameBitmaps, _sources, 0, orient, region, pt, frameCt, callback);
|
||||
}
|
||||
|
||||
protected function compositeFrames (frameBitmaps :Array, sources :Array, idx :int, orient :int,
|
||||
region :Rectangle, pt :Point, frameCt :int, callback :Function) :void
|
||||
{
|
||||
if (idx >= sources.length) {
|
||||
callback(new MultiFrameBitmap(frameBitmaps, _action.framesPerSecond));
|
||||
return;
|
||||
}
|
||||
|
||||
_sources[idx].frames.getFrames(orient, function (compBitmaps :MultiFrameBitmap) :void {
|
||||
for (var ii :int = 0; ii < frameCt; ii++) {
|
||||
frameBitmaps[ii].bitmapData.draw(compBitmaps.getFrame(ii).bitmapData);
|
||||
}
|
||||
compositeFrames(frameBitmaps, sources, idx+1, orient, region, pt, frameCt, callback);
|
||||
});
|
||||
}
|
||||
|
||||
public function equals (other :Object) :Boolean
|
||||
{
|
||||
return this === other;
|
||||
}
|
||||
|
||||
public function hashCode () :int
|
||||
{
|
||||
return _randHashCode;
|
||||
}
|
||||
|
||||
/** Used to cache our composited action frame images. */
|
||||
protected var _frameCache :Map;
|
||||
|
||||
protected static var _pending :Map = Maps.newMapOf(CompositedFramesKey);
|
||||
|
||||
/** The action for which we're compositing frames. */
|
||||
protected var _action :ActionSequence;
|
||||
|
||||
/** The number of orientations. */
|
||||
protected var _orientCount :int;
|
||||
|
||||
/** Our source components and action frames. */
|
||||
protected var _sources :Array;
|
||||
|
||||
/** We keep a hash code to use to identify us nearly-uniquely from the CompositedFramesKey. */
|
||||
protected var _randHashCode :int = int(Math.random() * Integer.MAX_VALUE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.cast {
|
||||
|
||||
import com.threerings.util.Hashable;
|
||||
|
||||
/** Used to cache composited frames for a particular action and
|
||||
* orientation. */
|
||||
public class CompositedFramesKey
|
||||
implements Hashable
|
||||
{
|
||||
public function CompositedFramesKey (owner :CompositedActionFrames, orient :int) {
|
||||
_orient = orient;
|
||||
_owner = owner;
|
||||
}
|
||||
|
||||
public function setOrient (orient :int) :void {
|
||||
_orient = orient;
|
||||
}
|
||||
|
||||
public function getOwner () :CompositedActionFrames {
|
||||
return _owner;
|
||||
}
|
||||
|
||||
public function equals (other :Object) :Boolean {
|
||||
var okey :CompositedFramesKey = CompositedFramesKey(other);
|
||||
return ((getOwner() == okey.getOwner()) &&
|
||||
(_orient == okey._orient));
|
||||
}
|
||||
|
||||
public function hashCode () :int {
|
||||
return _owner.hashCode() ^ _orient;
|
||||
}
|
||||
|
||||
protected var _orient :int;
|
||||
|
||||
protected var _owner :CompositedActionFrames;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.cast {
|
||||
|
||||
import flash.events.Event;
|
||||
|
||||
import com.threerings.cast.ComponentDataPack;
|
||||
import com.threerings.media.tile.TileDataPack;
|
||||
import com.threerings.util.Arrays;
|
||||
import com.threerings.util.DataPack;
|
||||
import com.threerings.util.Log;
|
||||
import com.threerings.util.Map;
|
||||
import com.threerings.util.Maps;
|
||||
import com.threerings.util.StringUtil;
|
||||
|
||||
public class DataPackComponentRepository
|
||||
implements ComponentRepository
|
||||
{
|
||||
private static var log :Log = Log.getLog(DataPackComponentRepository);
|
||||
|
||||
public function DataPackComponentRepository (compUrl :String)
|
||||
{
|
||||
_rootUrl = compUrl;
|
||||
loadComponentMeta(compUrl + "metadata.jar");
|
||||
}
|
||||
|
||||
protected function loadComponentMeta (compMetaUrl :String) :void
|
||||
{
|
||||
_metaPack = new TileDataPack(compMetaUrl, metaLoadingComplete);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the successful completion of datapack loading.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
protected function metaLoadingComplete (event :Event) :void
|
||||
{
|
||||
var actionXml :XML = _metaPack.getFileAsXML("actions.xml");
|
||||
var classesXml :XML = _metaPack.getFileAsXML("classes.xml");
|
||||
|
||||
for each (var aXml :XML in actionXml.action) {
|
||||
var action :ActionSequence = ActionSequence.fromXml(aXml);
|
||||
_actions.put(action.name, action);
|
||||
}
|
||||
|
||||
for each (var cXml :XML in classesXml.elements("class")) {
|
||||
var compClass :ComponentClass = ComponentClass.fromXml(cXml);
|
||||
_classes.put(compClass.name, compClass);
|
||||
}
|
||||
|
||||
parseComponentMap(_metaPack.getFileAsString("compmap.txt"));
|
||||
|
||||
// Notify them all and clear our list.
|
||||
for each (var func :Function in _notifyOnLoad) {
|
||||
func();
|
||||
}
|
||||
_notifyOnLoad = [];
|
||||
}
|
||||
|
||||
protected function parseComponentMap (map :String) :void
|
||||
{
|
||||
var lines :Array = map.split("\n");
|
||||
var tileCount :int = int(lines[0]);
|
||||
for (var ii :int = 1; ii < lines.length; ii++) {
|
||||
var line :String = lines[ii];
|
||||
var toks :Array = line.split(" := ");
|
||||
if (toks.length >= 3) {
|
||||
var id :int = int(toks[2]);
|
||||
var cclass :String = String(toks[0]);
|
||||
var cname :String = String(toks[1]);
|
||||
createComponent(id, cclass, cname);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a component and inserts it into the component table.
|
||||
*/
|
||||
protected function createComponent (componentId :int, cclass :String, cname :String) :void
|
||||
{
|
||||
// look up the component class information
|
||||
var clazz :ComponentClass = _classes.get(cclass);
|
||||
if (clazz == null) {
|
||||
log.warning("Non-existent component class",
|
||||
"class", cclass, "name", cname, "id", componentId);
|
||||
return;
|
||||
}
|
||||
|
||||
// create the component - we'll set the frame provider as soon as we can.
|
||||
var component :CharacterComponent = new CharacterComponent(componentId, cname, clazz, null);
|
||||
|
||||
// stick it into the appropriate tables
|
||||
_components.put(componentId, component);
|
||||
|
||||
// we have a hash of lists for mapping components by class/name
|
||||
var comps :Array = _classComps.get(cclass);
|
||||
if (comps == null) {
|
||||
comps = [];
|
||||
_classComps.put(cclass, comps);
|
||||
}
|
||||
if (!Arrays.contains(comps, component)) {
|
||||
comps.push(component);
|
||||
} else {
|
||||
log.info("Requested to register the same component twice?", "comp", component);
|
||||
}
|
||||
}
|
||||
|
||||
public function notifyOnLoad (func :Function) :void
|
||||
{
|
||||
_notifyOnLoad.push(func);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link CharacterComponent} object for the given component identifier.
|
||||
*/
|
||||
public function getComponent (componentId :int) :CharacterComponent
|
||||
{
|
||||
var component :CharacterComponent = _components.get(componentId);
|
||||
if (component == null) {
|
||||
throw new NoSuchComponentError(componentId);
|
||||
}
|
||||
return component;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link CharacterComponent} object with the given component class and name.
|
||||
*/
|
||||
public function getComponentByName (className :String, compName :String) :CharacterComponent
|
||||
{
|
||||
// look up the list for that class
|
||||
var comps :Array = _classComps.get(className);
|
||||
if (comps != null) {
|
||||
for each (var comp :CharacterComponent in comps) {
|
||||
if (comp.name == compName) {
|
||||
return comp;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new NoSuchComponentError(className + "/" + compName);
|
||||
}
|
||||
|
||||
public function load (compIds :Array, notify :Function) :void
|
||||
{
|
||||
var complete :Boolean = true;
|
||||
|
||||
var toLoad :Array = [];
|
||||
|
||||
for each (var cid :int in compIds) {
|
||||
var comp :CharacterComponent = getComponent(cid);
|
||||
if (!comp.isLoaded()) {
|
||||
toLoad.push(comp);
|
||||
}
|
||||
}
|
||||
|
||||
var waiting :int = toLoad.length;
|
||||
|
||||
if (waiting == 0) {
|
||||
notify();
|
||||
} else {
|
||||
for each (var load :CharacterComponent in toLoad) {
|
||||
load.notifyOnLoad(function() :void {
|
||||
waiting--;
|
||||
if (waiting == 0) {
|
||||
notify();
|
||||
}
|
||||
});
|
||||
loadComponent(load);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function loadComponent (comp :CharacterComponent) :void
|
||||
{
|
||||
var packName :String = comp.componentClass.name + "/" + comp.name;
|
||||
var pack :ComponentDataPack = _packs.get(packName);
|
||||
if (pack == null || !pack.isComplete()) {
|
||||
loadPack(packName, comp);
|
||||
} else {
|
||||
comp.setFrameProvider(pack);
|
||||
}
|
||||
}
|
||||
|
||||
protected function loadPack (packName :String, comp :CharacterComponent) :void
|
||||
{
|
||||
var pack :ComponentDataPack = _packs.get(packName);
|
||||
|
||||
if (pack != null && pack.isComplete()) {
|
||||
// Already loaded
|
||||
return;
|
||||
}
|
||||
|
||||
var completeListener :Function = function () :void {
|
||||
comp.setFrameProvider(_packs.get(packName));
|
||||
}
|
||||
|
||||
var listeners :Array = _packListeners.get(packName);
|
||||
if (listeners == null) {
|
||||
listeners = [];
|
||||
_packListeners.put(packName, listeners);
|
||||
|
||||
var packCompleteListener :Function = function () :void {
|
||||
for each (var listener :Function in listeners) {
|
||||
listener();
|
||||
}
|
||||
_packListeners.remove(packName);
|
||||
}
|
||||
|
||||
pack = new ComponentDataPack(getPackUrl(packName), _actions, packCompleteListener);
|
||||
_packs.put(packName, pack);
|
||||
}
|
||||
|
||||
listeners.push(completeListener);
|
||||
}
|
||||
|
||||
protected function getPackUrl (packName :String) :String
|
||||
{
|
||||
return _rootUrl + packName + "/components.jar";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link ComponentClass} with the specified name or null if none exists with that
|
||||
* name.
|
||||
*/
|
||||
public function getComponentClass (className :String) :ComponentClass
|
||||
{
|
||||
return _classes.get(className);
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates over the {@link ComponentClass} instances representing all available character
|
||||
* component classes.
|
||||
*/
|
||||
public function getComponentClasses () :Array
|
||||
{
|
||||
return _classes.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates over the {@link ActionSequence} instances representing every available action
|
||||
* sequence.
|
||||
*/
|
||||
public function getActionSequences () :Array
|
||||
{
|
||||
return _actions.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates over the component ids of all components in the specified class.
|
||||
*/
|
||||
public function getComponentIds (compClass :ComponentClass) :Array
|
||||
{
|
||||
var result :Array = [];
|
||||
|
||||
_classes.forEach(function (key :int, value :CharacterComponent) :Boolean {
|
||||
if (value.componentClass.equals(compClass)) {
|
||||
result.push(key);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Everyone who cares when we're loaded. */
|
||||
protected var _notifyOnLoad :Array = [];
|
||||
|
||||
/** Contains the component meta-data for all components. */
|
||||
protected var _metaPack : DataPack;
|
||||
|
||||
/** The URL for our components. */
|
||||
protected var _rootUrl :String;
|
||||
|
||||
/** Map of pack name to listeners waiting for that pack to resolve. */
|
||||
protected var _packListeners :Map = Maps.newMapOf(String);
|
||||
|
||||
/** The map of packs we've already loaded up and resolved. */
|
||||
protected var _packs :Map = Maps.newMapOf(String);
|
||||
|
||||
/** All our component classes by name. */
|
||||
protected var _classes :Map = Maps.newMapOf(String);
|
||||
|
||||
/** All our action sequences by name. */
|
||||
protected var _actions :Map = Maps.newMapOf(String);
|
||||
|
||||
/** The map of componentId to CharacterComponent */
|
||||
protected var _components :Map = Maps.newMapOf(int);
|
||||
|
||||
/** A table of component lists indexed on classname. */
|
||||
protected var _classComps :Map = Maps.newMapOf(String);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.cast {
|
||||
|
||||
import flash.display.Bitmap;
|
||||
import flash.display.Sprite;
|
||||
|
||||
import flash.geom.Point;
|
||||
|
||||
import flash.events.Event;
|
||||
|
||||
import com.threerings.media.Tickable;
|
||||
import com.threerings.util.StringUtil;
|
||||
|
||||
public class MultiFrameBitmap extends Sprite
|
||||
implements Tickable
|
||||
{
|
||||
public function MultiFrameBitmap (frames :Array, fps :Number)
|
||||
{
|
||||
_frames = frames;
|
||||
_bitmap = new Bitmap();
|
||||
_fps = fps;
|
||||
_frameRate = fps / 1000.0;
|
||||
addChild(_bitmap);
|
||||
setFrame(0);
|
||||
}
|
||||
|
||||
public function tick (tickStamp :int) :void
|
||||
{
|
||||
if (_start == 0) {
|
||||
_start = tickStamp;
|
||||
}
|
||||
|
||||
var elapsedTime :int = (tickStamp - _start);
|
||||
var frameIndex :int = Math.floor(elapsedTime * _frameRate) % _frames.length;
|
||||
setFrame(frameIndex);
|
||||
}
|
||||
|
||||
protected function setFrame (index :int) :void
|
||||
{
|
||||
if (_curFrameIndex != index) {
|
||||
_bitmap.bitmapData = Bitmap(_frames[index]).bitmapData;
|
||||
_curFrameIndex = index;
|
||||
}
|
||||
}
|
||||
|
||||
public function getFrame (index :int) :Bitmap
|
||||
{
|
||||
return _frames[index];
|
||||
}
|
||||
|
||||
public function getFrameCount () :int
|
||||
{
|
||||
return _frames.length;
|
||||
}
|
||||
|
||||
public function hitTest (stageX :int, stageY :int) :Boolean
|
||||
{
|
||||
if (_curFrameIndex == -1) {
|
||||
return false;
|
||||
} else {
|
||||
if (!_bitmap.hitTestPoint(stageX, stageY, true)) {
|
||||
// Doesn't even hit the bounds...
|
||||
return false;
|
||||
}
|
||||
// Check the actual pixels...
|
||||
var pt :Point = _bitmap.globalToLocal(new Point(stageX, stageY));
|
||||
return _bitmap.bitmapData.hitTest(new Point(0, 0), 0, pt);
|
||||
}
|
||||
}
|
||||
|
||||
public function clone () :MultiFrameBitmap
|
||||
{
|
||||
return new MultiFrameBitmap(_frames, _fps);
|
||||
}
|
||||
|
||||
protected var _frames :Array;
|
||||
|
||||
protected var _bitmap :Bitmap;
|
||||
|
||||
protected var _start :int;
|
||||
|
||||
protected var _frameRate :Number;
|
||||
|
||||
protected var _fps :Number;
|
||||
|
||||
protected var _curFrameIndex :int = -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.cast {
|
||||
|
||||
/**
|
||||
* Thrown when an attempt is made to retrieve a non-existent character
|
||||
* component from the component repository.
|
||||
*/
|
||||
public class NoSuchComponentError extends Error
|
||||
{
|
||||
public function NoSuchComponentError (comp :*)
|
||||
{
|
||||
super((comp is int) ? ("No component with id '" + int(comp) + "'") :
|
||||
("No component named'" + String(comp) + "'"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.ClassUtil;
|
||||
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;
|
||||
import com.threerings.util.XmlUtil;
|
||||
|
||||
/** 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 = null;
|
||||
|
||||
/** The component, if any, for which this override is appropriate. */
|
||||
public var component :String = null;
|
||||
|
||||
/** 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 = XmlUtil.getIntAttr(xml, "renderPriority");
|
||||
override.action = XmlUtil.getStringAttr(xml, "action", null);
|
||||
override.component = XmlUtil.getStringAttr(xml, "component", null);
|
||||
|
||||
override.orients = Sets.newSetOf(int);
|
||||
for each (var orient :int in toOrientArray(
|
||||
XmlUtil.getStringAttr(xml, "orients", null))) {
|
||||
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 (((this.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 + "]";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.cast {
|
||||
|
||||
/**
|
||||
* Actions are referenced by name and this interface defines constants for
|
||||
* standard actions and action suffixes used by the shadow and cropping
|
||||
* support.
|
||||
*/
|
||||
public class StandardActions
|
||||
{
|
||||
/** The name of the standard standing action. */
|
||||
public static const STANDING :String = "standing";
|
||||
|
||||
/** The name of the standard walking action. */
|
||||
public static const WALKING :String = "walking";
|
||||
|
||||
/** A special action sub-type for shadow imagery. */
|
||||
public static const SHADOW_TYPE :String = "shadow";
|
||||
|
||||
/** A special action sub-type for crop imagery. */
|
||||
public static const CROP_TYPE :String = "crop";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.cast {
|
||||
|
||||
import flash.display.Bitmap;
|
||||
|
||||
import flash.geom.Rectangle;
|
||||
|
||||
import com.threerings.media.tile.Tile;
|
||||
import com.threerings.media.tile.TileSet;
|
||||
import com.threerings.util.Map;
|
||||
import com.threerings.util.Maps;
|
||||
|
||||
public class TileSetFrameImage
|
||||
implements ActionFrames
|
||||
{
|
||||
public function TileSetFrameImage (set :TileSet, actseq :ActionSequence,
|
||||
dx :int = 0, dy :int = 0)
|
||||
{
|
||||
_set = set;
|
||||
_actseq = actseq;
|
||||
_dx = dx;
|
||||
_dy = dy;
|
||||
|
||||
// compute these now to avoid pointless recomputation later
|
||||
_ocount = actseq.orients.length;
|
||||
_fcount = set.getTileCount() / _ocount;
|
||||
|
||||
// create our mapping from orientation to animation sequence index
|
||||
for (var ii :int = 0; ii < _ocount; ii++) {
|
||||
_orients.put(actseq.orients[ii], ii);
|
||||
}
|
||||
}
|
||||
|
||||
public function getFrames (orient :int, callback :Function) :void
|
||||
{
|
||||
var frames :Array = new Array(_fcount);
|
||||
getFramesFromTiles(frames, 0, orient, callback);
|
||||
}
|
||||
|
||||
protected function getFramesFromTiles (frames :Array, idx :int, orient :int,
|
||||
callback :Function) :void
|
||||
{
|
||||
if (idx >= frames.length) {
|
||||
callback(new MultiFrameBitmap(frames, _actseq.framesPerSecond));
|
||||
} else {
|
||||
var tile :Tile = getTile(orient, idx);
|
||||
tile.notifyOnLoad(function() :void {
|
||||
frames[idx] = Bitmap(tile.getImage());
|
||||
getFramesFromTiles(frames, idx + 1, orient, callback);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public function getOrientationCount () :int
|
||||
{
|
||||
return _ocount;
|
||||
}
|
||||
|
||||
protected function getTileIndex (orient :int, index :int) :int
|
||||
{
|
||||
return _orients.get(orient) * _fcount + index;
|
||||
}
|
||||
|
||||
protected function getTile (orient :int, index :int) :Tile
|
||||
{
|
||||
return _set.getTile(getTileIndex(orient, index));
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public function getXOrigin (orient :int, index :int) :int
|
||||
{
|
||||
return _actseq.origin.x;
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public function getYOrigin (orient :int, index :int) :int
|
||||
{
|
||||
return _actseq.origin.y;
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public function cloneColorized (zations :Array) :ActionFrames
|
||||
{
|
||||
return new TileSetFrameImage(_set.cloneWithZations(zations), _actseq);
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public function cloneTranslated (dx :int, dy :int) :ActionFrames
|
||||
{
|
||||
return new TileSetFrameImage(_set, _actseq, dx, dy);
|
||||
}
|
||||
|
||||
/** The tileset from which we obtain our frame images. */
|
||||
protected var _set :TileSet;
|
||||
|
||||
/** The action sequence for which we're providing frame images. */
|
||||
protected var _actseq :ActionSequence;
|
||||
|
||||
/** A translation to apply to the images. */
|
||||
protected var _dx :int;
|
||||
protected var _dy :int;
|
||||
|
||||
/** Frame and orientation counts. */
|
||||
protected var _fcount :int;
|
||||
protected var _ocount :int;
|
||||
|
||||
/** A mapping from orientation code to animation sequence index. */
|
||||
protected var _orients :Map = Maps.newMapOf(int);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.flex {
|
||||
|
||||
import mx.controls.List;
|
||||
|
||||
import mx.core.EdgeMetrics;
|
||||
|
||||
/**
|
||||
* A list that can be configured with the vertical scrollbar on the left or right side.
|
||||
*/
|
||||
public class AmbidextrousList extends List
|
||||
{
|
||||
/**
|
||||
* Get whether the scrollbar is on the left side.
|
||||
*/
|
||||
public function get scrollBarOnLeft () :Boolean
|
||||
{
|
||||
return _scrollLeft;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to place the scrollbar on the left or right side.
|
||||
*/
|
||||
public function set scrollBarOnLeft (scrollLeft :Boolean) :void
|
||||
{
|
||||
_scrollLeft = scrollLeft;
|
||||
|
||||
scrollAreaChanged = true;
|
||||
invalidateDisplayList();
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
override public function get viewMetrics () :EdgeMetrics
|
||||
{
|
||||
var em :EdgeMetrics = super.viewMetrics;
|
||||
|
||||
if (_scrollLeft && verticalScrollBar && verticalScrollBar.visible) {
|
||||
em.right -= verticalScrollBar.minWidth;
|
||||
em.left += verticalScrollBar.minWidth;
|
||||
}
|
||||
|
||||
return em;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
override protected function updateDisplayList (uw :Number, uh :Number) :void
|
||||
{
|
||||
super.updateDisplayList(uw, uh);
|
||||
|
||||
if (_scrollLeft) {
|
||||
var vm :EdgeMetrics = viewMetrics;
|
||||
verticalScrollBar.move(vm.left - verticalScrollBar.minWidth, vm.top);
|
||||
}
|
||||
}
|
||||
|
||||
/** Do we want to have the scrollbar on the left? */
|
||||
protected var _scrollLeft :Boolean;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.flex {
|
||||
|
||||
import flash.display.DisplayObjectContainer;
|
||||
|
||||
import flash.events.Event;
|
||||
import flash.events.FocusEvent;
|
||||
import flash.events.KeyboardEvent;
|
||||
import flash.events.MouseEvent;
|
||||
|
||||
import flash.ui.Keyboard;
|
||||
|
||||
import mx.containers.HBox;
|
||||
|
||||
import mx.core.Application;
|
||||
|
||||
import mx.controls.TextInput;
|
||||
|
||||
import mx.events.FlexEvent;
|
||||
|
||||
import com.threerings.util.Arrays;
|
||||
import com.threerings.util.DelayUtil;
|
||||
import com.threerings.util.StringUtil;
|
||||
|
||||
import com.threerings.flex.CommandButton;
|
||||
|
||||
import com.threerings.crowd.chat.client.ChatDirector;
|
||||
import com.threerings.crowd.chat.data.ChatCodes;
|
||||
|
||||
import com.threerings.crowd.util.CrowdContext;
|
||||
|
||||
/**
|
||||
* The chat control widget.
|
||||
*/
|
||||
public class ChatControl extends HBox
|
||||
{
|
||||
/**
|
||||
* Request focus for the oldest ChatControl.
|
||||
*/
|
||||
public static function grabFocus () :void
|
||||
{
|
||||
if (_controls.length > 0) {
|
||||
(_controls[0] as ChatControl).setFocus();
|
||||
}
|
||||
}
|
||||
|
||||
public function ChatControl (
|
||||
ctx :CrowdContext, sendButtonLabel :String = "send",
|
||||
height :Number = NaN, controlHeight :Number = NaN)
|
||||
{
|
||||
_ctx = ctx;
|
||||
_chatDtr = _ctx.getChatDirector();
|
||||
|
||||
this.height = height;
|
||||
styleName = "chatControl";
|
||||
|
||||
addChild(_txt = new ChatInput());
|
||||
_txt.addEventListener(FlexEvent.ENTER, sendChat, false, 0, true);
|
||||
_txt.addEventListener(KeyboardEvent.KEY_UP, handleKeyUp);
|
||||
|
||||
_but = new CommandButton(sendButtonLabel, sendChat);
|
||||
addChild(_but);
|
||||
|
||||
if (!isNaN(controlHeight)) {
|
||||
_txt.height = controlHeight;
|
||||
_but.height = controlHeight;
|
||||
}
|
||||
|
||||
addEventListener(Event.ADDED_TO_STAGE, handleAddRemove);
|
||||
addEventListener(Event.REMOVED_FROM_STAGE, handleAddRemove);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides access to the text field we use to accept chat.
|
||||
*/
|
||||
public function get chatInput () :ChatInput
|
||||
{
|
||||
return _txt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides access to the send button.
|
||||
*/
|
||||
public function get sendButton () :CommandButton
|
||||
{
|
||||
return _but;
|
||||
}
|
||||
|
||||
override public function set enabled (en :Boolean) :void
|
||||
{
|
||||
super.enabled = en;
|
||||
if (_txt != null) {
|
||||
_txt.enabled = en;
|
||||
_but.enabled = en;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request focus to this chat control.
|
||||
*/
|
||||
override public function setFocus () :void
|
||||
{
|
||||
_txt.setFocus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the chat director to which we should send our chat. Pass null to restore our
|
||||
* default chat director.
|
||||
*/
|
||||
public function setChatDirector (chatDtr :ChatDirector) :void
|
||||
{
|
||||
_chatDtr = (chatDtr == null) ? _ctx.getChatDirector() : chatDtr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the background color of the text entry area.
|
||||
*/
|
||||
public function setChatColor (color :uint) :void
|
||||
{
|
||||
_txt.setStyle("backgroundColor", color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles FlexEvent.ENTER and the action from the send button.
|
||||
*/
|
||||
protected function sendChat (... ignored) :void
|
||||
{
|
||||
var message :String = StringUtil.trim(_txt.text);
|
||||
if ("" == message) {
|
||||
return;
|
||||
}
|
||||
|
||||
var result :String = _chatDtr.requestChat(null, message, true);
|
||||
if (result != ChatCodes.SUCCESS) {
|
||||
_chatDtr.displayFeedback(null, result);
|
||||
return;
|
||||
}
|
||||
|
||||
// If there was no error, clear the entry area in prep for the next entry event
|
||||
DelayUtil.delayFrame(function () : void {
|
||||
// WORKAROUND
|
||||
// Originally we cleared the text immediately, but with flex 3.2 this broke
|
||||
// for *some* people. Weird! We're called from the event dispatcher for the
|
||||
// enter key, so it's possible that the default action is booching it?
|
||||
// In any case, this could possibly be removed in the future by the ambitious.
|
||||
// Note also: Flex's built-in callLater() doesn't work, but DelayUtil does. WTF?!?
|
||||
_txt.text = "";
|
||||
});
|
||||
_histidx = -1;
|
||||
}
|
||||
|
||||
protected function scrollHistory (next :Boolean) :void
|
||||
{
|
||||
var size :int = _chatDtr.getCommandHistorySize();
|
||||
if ((_histidx == -1) || (_histidx == size)) {
|
||||
_curLine = _txt.text;
|
||||
_histidx = size;
|
||||
}
|
||||
|
||||
_histidx = (next) ? Math.min(_histidx + 1, size)
|
||||
: Math.max(_histidx - 1, 0);
|
||||
var text :String = (_histidx == size) ? _curLine : _chatDtr.getCommandHistory(_histidx);
|
||||
_txt.text = text;
|
||||
_txt.setSelection(text.length, text.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles Event.ADDED_TO_STAGE and Event.REMOVED_FROM_STAGE.
|
||||
*/
|
||||
protected function handleAddRemove (event :Event) :void
|
||||
{
|
||||
if (event.type == Event.ADDED_TO_STAGE) {
|
||||
// set up any already-configured text
|
||||
_txt.text = _curLine;
|
||||
_histidx = -1;
|
||||
|
||||
// request focus
|
||||
callLater(_txt.setFocus);
|
||||
_controls.push(this);
|
||||
|
||||
} else {
|
||||
_curLine = _txt.text;
|
||||
Arrays.removeAll(_controls, this);
|
||||
}
|
||||
}
|
||||
|
||||
protected function handleKeyUp (event :KeyboardEvent) :void
|
||||
{
|
||||
switch (event.keyCode) {
|
||||
case Keyboard.UP: scrollHistory(false); break;
|
||||
case Keyboard.DOWN: scrollHistory(true); break;
|
||||
}
|
||||
}
|
||||
|
||||
/** Our client-side context. */
|
||||
protected var _ctx :CrowdContext;
|
||||
|
||||
/** The director to which we are sending chat requests. */
|
||||
protected var _chatDtr :ChatDirector;
|
||||
|
||||
/** The place where the user may enter chat. */
|
||||
protected var _txt :ChatInput;
|
||||
|
||||
/** The current index in the chat command history. */
|
||||
protected var _histidx :int = -1;
|
||||
|
||||
/** The button for sending chat. */
|
||||
protected var _but :CommandButton;
|
||||
|
||||
/** An array of the currently shown-controls. */
|
||||
protected static var _controls :Array = [];
|
||||
|
||||
/** The preserved current line of text when traversing history or carried between instances of
|
||||
* ChatControl. */
|
||||
protected static var _curLine :String;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.flex {
|
||||
|
||||
import flash.display.DisplayObjectContainer;
|
||||
|
||||
import flash.events.Event;
|
||||
|
||||
import mx.controls.TextArea;
|
||||
|
||||
import com.threerings.crowd.chat.client.ChatDirector;
|
||||
import com.threerings.crowd.chat.client.ChatDisplay;
|
||||
import com.threerings.crowd.chat.data.ChatMessage;
|
||||
import com.threerings.crowd.chat.data.UserMessage;
|
||||
import com.threerings.crowd.util.CrowdContext;
|
||||
|
||||
/**
|
||||
* A very simple chat display.
|
||||
*/
|
||||
public class ChatDisplayBox extends TextArea
|
||||
implements ChatDisplay
|
||||
{
|
||||
public function ChatDisplayBox (ctx :CrowdContext)
|
||||
{
|
||||
_ctx = ctx;
|
||||
this.editable = false;
|
||||
|
||||
// TODO
|
||||
width = 400;
|
||||
height = 150;
|
||||
|
||||
addEventListener(Event.ADDED_TO_STAGE, handleAddRemove);
|
||||
addEventListener(Event.REMOVED_FROM_STAGE, handleAddRemove);
|
||||
}
|
||||
|
||||
// documentation inherited from interface ChatDisplay
|
||||
public function clear () :void
|
||||
{
|
||||
this.htmlText = "";
|
||||
}
|
||||
|
||||
// documentation inherited from interface ChatDisplay
|
||||
public function displayMessage (msg :ChatMessage) :void
|
||||
{
|
||||
if (!_scrollBot) {
|
||||
_scrollBot = (verticalScrollPosition == maxVerticalScrollPosition);
|
||||
}
|
||||
|
||||
// display the message
|
||||
if (msg is UserMessage) {
|
||||
this.htmlText += "<font color=\"red\"><" +
|
||||
(msg as UserMessage).speaker + "></font> ";
|
||||
}
|
||||
this.htmlText += msg.message;
|
||||
}
|
||||
|
||||
// handle us being added or removed from the stage
|
||||
protected function handleAddRemove (event :Event) :void
|
||||
{
|
||||
var chatdir :ChatDirector = _ctx.getChatDirector();
|
||||
if (event.type == Event.ADDED_TO_STAGE) {
|
||||
chatdir.addChatDisplay(this);
|
||||
} else {
|
||||
chatdir.removeChatDisplay(this);
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
override protected function updateDisplayList (uw :Number, uh :Number) :void
|
||||
{
|
||||
super.updateDisplayList(uw, uh);
|
||||
|
||||
if (_scrollBot) {
|
||||
verticalScrollPosition = maxVerticalScrollPosition;
|
||||
_scrollBot = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** The giver of life. */
|
||||
protected var _ctx :CrowdContext;
|
||||
|
||||
protected var _scrollBot :Boolean;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.flex {
|
||||
|
||||
import flash.events.FocusEvent;
|
||||
|
||||
import flash.text.TextField;
|
||||
|
||||
import mx.controls.TextInput;
|
||||
|
||||
import com.threerings.text.TextFieldUtil;
|
||||
|
||||
/**
|
||||
* The class name of an image to use as the input prompt.
|
||||
*/
|
||||
[Style(name="prompt", type="Class")]
|
||||
|
||||
/**
|
||||
* A special TextInput for entering Chat. One of these is used in ChatControl.
|
||||
*
|
||||
* A standard TextInput has the stupid behavior of selecting all the text when it receives
|
||||
* focus. Disable that so that we can receive focus and people can type and we don't blow away
|
||||
* whatever they had before.
|
||||
*/
|
||||
public class ChatInput extends TextInput
|
||||
{
|
||||
public function ChatInput ()
|
||||
{
|
||||
width = 147;
|
||||
showPrompt(true);
|
||||
}
|
||||
|
||||
override public function stylesInitialized () :void
|
||||
{
|
||||
super.stylesInitialized();
|
||||
|
||||
checkShowPrompt();
|
||||
}
|
||||
|
||||
override public function styleChanged (styleProp :String) :void
|
||||
{
|
||||
super.styleChanged(styleProp);
|
||||
|
||||
if (styleProp == "prompt") {
|
||||
checkShowPrompt();
|
||||
}
|
||||
}
|
||||
|
||||
protected function checkShowPrompt () :void
|
||||
{
|
||||
showPrompt(focusManager == null || focusManager.getFocus() != this);
|
||||
}
|
||||
|
||||
protected function showPrompt (show :Boolean) :void
|
||||
{
|
||||
setStyle("backgroundImage", (show && ("" == text)) ? getStyle("prompt") : undefined);
|
||||
}
|
||||
|
||||
override protected function createChildren () :void
|
||||
{
|
||||
super.createChildren();
|
||||
|
||||
// For some reason, in embeds, during certain circumstances, it's really hard to focus
|
||||
// chat. This makes that work better. The problem was *not* testable locally, only
|
||||
// on embeds in production. (I don't know why.)
|
||||
TextFieldUtil.setFocusable(TextField(textField));
|
||||
}
|
||||
|
||||
override protected function focusInHandler (event :FocusEvent) :void
|
||||
{
|
||||
var oldValue :Boolean = textField.selectable;
|
||||
textField.selectable = false;
|
||||
try {
|
||||
super.focusInHandler(event);
|
||||
|
||||
} finally {
|
||||
textField.selectable = oldValue;
|
||||
}
|
||||
showPrompt(false);
|
||||
}
|
||||
|
||||
override protected function focusOutHandler (event :FocusEvent) :void
|
||||
{
|
||||
super.focusOutHandler(event);
|
||||
showPrompt(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.flex {
|
||||
|
||||
import flash.events.MouseEvent;
|
||||
|
||||
import mx.controls.Button;
|
||||
|
||||
import com.threerings.util.CommandEvent;
|
||||
|
||||
/**
|
||||
* A command button simply dispatches a Controller command (with an optional
|
||||
* argument) when it is clicked.
|
||||
*/
|
||||
public class CommandButton extends Button
|
||||
{
|
||||
/**
|
||||
* Create a command button.
|
||||
*
|
||||
* @param label the label text for the button.
|
||||
* @param cmdOrFn either a String, which will be the CommandEvent command to dispatch,
|
||||
* or a function, which will be called when clicked.
|
||||
* @param arg the argument for the CommentEvent or the function. If the arg is an Array
|
||||
* then those parameters are used for calling the function.
|
||||
*/
|
||||
public function CommandButton (label :String = null, cmdOrFn :* = null, arg :Object = null)
|
||||
{
|
||||
validateCmd(cmdOrFn);
|
||||
this.label = label;
|
||||
_cmdOrFn = cmdOrFn;
|
||||
_arg = arg;
|
||||
buttonMode = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the command and argument to be issued when this button is pressed.
|
||||
*/
|
||||
public function setCommand (cmd :String, arg :Object = null) :void
|
||||
{
|
||||
_cmdOrFn = cmd;
|
||||
_arg = arg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a callback function to call when the button is pressed.
|
||||
*/
|
||||
public function setCallback (fn :Function, arg :Object = null) :void
|
||||
{
|
||||
_cmdOrFn = fn;
|
||||
_arg = arg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the argument (or arguments, in an Array).
|
||||
*/
|
||||
public function getArg () :Object
|
||||
{
|
||||
return _arg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emulate a user click, dispatching all relevant events.
|
||||
*/
|
||||
public function activate () :void
|
||||
{
|
||||
dispatchEvent(new MouseEvent(MouseEvent.CLICK));
|
||||
}
|
||||
|
||||
override public function set enabled (val :Boolean) :void
|
||||
{
|
||||
super.enabled = val;
|
||||
buttonMode = val;
|
||||
}
|
||||
|
||||
override protected function clickHandler (event :MouseEvent) :void
|
||||
{
|
||||
super.clickHandler(event);
|
||||
processCommandClick(this, event, _cmdOrFn, getArg());
|
||||
}
|
||||
|
||||
internal static function validateCmd (cmdOrFn :Object) :void
|
||||
{
|
||||
if (cmdOrFn != null && !(cmdOrFn is String) && !(cmdOrFn is Function)) {
|
||||
// runtime errors suck, but this is actionscript
|
||||
throw new Error("cmdOrFn must be a String or Function.");
|
||||
}
|
||||
}
|
||||
|
||||
internal static function processCommandClick (
|
||||
button :Button, event :MouseEvent, cmdOrFn :Object, arg :Object) :void
|
||||
{
|
||||
if (button.enabled) {
|
||||
event.stopImmediatePropagation();
|
||||
if (arg == null && button.toggle) {
|
||||
arg = button.selected;
|
||||
}
|
||||
CommandEvent.dispatch(button, cmdOrFn, arg);
|
||||
}
|
||||
}
|
||||
|
||||
/** The command (String) to submit, or the function (Function) to call
|
||||
* when clicked, */
|
||||
protected var _cmdOrFn :Object;
|
||||
|
||||
/** The argument that accompanies our command. */
|
||||
protected var _arg :Object;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.flex {
|
||||
|
||||
import flash.events.MouseEvent;
|
||||
|
||||
import mx.controls.CheckBox;
|
||||
|
||||
import com.threerings.util.CommandEvent;
|
||||
|
||||
/**
|
||||
* A checkbox that dispatches a command (or calls a function) when it is toggled.
|
||||
*/
|
||||
public class CommandCheckBox extends CheckBox
|
||||
{
|
||||
/**
|
||||
* Create a command checkbox.
|
||||
*
|
||||
* @param label the label text for the checkbox.
|
||||
* @param cmdOrFn either a String, which will be the CommandEvent command to dispatch,
|
||||
* or a function, which will be called when clicked.
|
||||
* @param arg the argument for the CommentEvent or the function. If the arg is an Array
|
||||
* then those parameters are used for calling the function.
|
||||
* Note that if arg is null, the actual argument passed will be the 'selected' state of the
|
||||
* checkbox. If you really want to call a method with no args, specify arg as an emtpy Array.
|
||||
*/
|
||||
public function CommandCheckBox (label :String = null, cmdOrFn :* = null, arg :Object = null)
|
||||
{
|
||||
CommandButton.validateCmd(cmdOrFn);
|
||||
this.label = label;
|
||||
_cmdOrFn = cmdOrFn;
|
||||
_arg = arg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the command and argument to be issued when this button is pressed.
|
||||
*/
|
||||
public function setCommand (cmd :String, arg :Object = null) :void
|
||||
{
|
||||
_cmdOrFn = cmd;
|
||||
_arg = arg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a callback function to call when the button is pressed.
|
||||
*/
|
||||
public function setCallback (fn :Function, arg :Object = null) :void
|
||||
{
|
||||
_cmdOrFn = fn;
|
||||
_arg = arg;
|
||||
}
|
||||
|
||||
override protected function clickHandler (event :MouseEvent) :void
|
||||
{
|
||||
super.clickHandler(event);
|
||||
CommandButton.processCommandClick(this, event, _cmdOrFn, _arg);
|
||||
}
|
||||
|
||||
/** The command (String) to submit, or the function (Function) to call
|
||||
* when clicked, */
|
||||
protected var _cmdOrFn :Object;
|
||||
|
||||
/** The argument that accompanies our command. */
|
||||
protected var _arg :Object;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.flex {
|
||||
|
||||
import flash.events.Event;
|
||||
|
||||
import mx.controls.ComboBox;
|
||||
import mx.events.ListEvent;
|
||||
|
||||
import com.threerings.util.CommandEvent;
|
||||
import com.threerings.util.Util;
|
||||
|
||||
/**
|
||||
* A combo box that dispatches a command (or calls a callback) when an item is selected.
|
||||
* The argument will be the 'data' value of the selected item.
|
||||
* NOTE: Unlike the other Command* controls, CommandComboBox allows a null cmd/callback
|
||||
* to be specified.
|
||||
*/
|
||||
public class CommandComboBox extends ComboBox
|
||||
{
|
||||
/** The field of the selectedItem object used as the 'data'. If this property is null,
|
||||
* then the item is the data. */
|
||||
public var dataField :String = "data";
|
||||
|
||||
/**
|
||||
* Create a command combobox.
|
||||
*
|
||||
* @param cmdOrFn either a String, which will be the CommandEvent command to dispatch,
|
||||
* or a function, which will be called when changed.
|
||||
*/
|
||||
public function CommandComboBox (cmdOrFn :* = null)
|
||||
{
|
||||
CommandButton.validateCmd(cmdOrFn);
|
||||
_cmdOrFn = cmdOrFn;
|
||||
|
||||
addEventListener(ListEvent.CHANGE, handleChange, false, int.MIN_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the command and argument to be issued when this button is pressed.
|
||||
*/
|
||||
public function setCommand (cmd :String) :void
|
||||
{
|
||||
_cmdOrFn = cmd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a callback function to call when the button is pressed.
|
||||
*/
|
||||
public function setCallback (fn :Function) :void
|
||||
{
|
||||
_cmdOrFn = fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the selectedItem based on the data field.
|
||||
*/
|
||||
public function set selectedData (data :Object) :void
|
||||
{
|
||||
for (var ii :int = 0; ii < dataProvider.length; ++ii) {
|
||||
if (Util.equals(data, itemToData(dataProvider[ii]))) {
|
||||
this.selectedIndex = ii;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// not found, clear the selection
|
||||
this.selectedIndex = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* The value that will be passed to the command or function based on dataField and the
|
||||
* current selected item.
|
||||
*/
|
||||
public function get selectedData () :Object
|
||||
{
|
||||
return itemToData(this.selectedItem);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the data from the specified item. This can be expanded in the future
|
||||
* if we provide a 'dataFunction'.
|
||||
*/
|
||||
protected function itemToData (item :Object) :Object
|
||||
{
|
||||
if (item != null && dataField != null) {
|
||||
try {
|
||||
return item[dataField];
|
||||
} catch (re :ReferenceError) {
|
||||
// fallback to just returning the item
|
||||
}
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
protected function handleChange (event :ListEvent) :void
|
||||
{
|
||||
if (_cmdOrFn != null && this.selectedIndex != -1) {
|
||||
CommandEvent.dispatch(this, _cmdOrFn, selectedData);
|
||||
}
|
||||
}
|
||||
|
||||
/** The command (String) to submit, or the function (Function) to call
|
||||
* when changed, */
|
||||
protected var _cmdOrFn :Object;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.flex {
|
||||
|
||||
import flash.events.MouseEvent;
|
||||
|
||||
import mx.controls.LinkButton;
|
||||
|
||||
import com.threerings.util.CommandEvent;
|
||||
|
||||
/**
|
||||
* A CommandLinkButton looks like a link but dispatches a Controller command
|
||||
* when clicked.
|
||||
*/
|
||||
public class CommandLinkButton extends LinkButton
|
||||
{
|
||||
/**
|
||||
* Create a command link button.
|
||||
*
|
||||
* @param label the label text for the button.
|
||||
* @param cmdOrFn either a String, which will be the CommandEvent command to dispatch,
|
||||
* or a function, which will be called when clicked.
|
||||
* @param arg the argument for the CommentEvent or the function. If the arg is an Array
|
||||
* then those parameters are used for calling the function.
|
||||
*/
|
||||
public function CommandLinkButton (label :String = null, cmdOrFn :* = null, arg :Object = null)
|
||||
{
|
||||
CommandButton.validateCmd(cmdOrFn);
|
||||
this.label = label;
|
||||
_cmdOrFn = cmdOrFn;
|
||||
_arg = arg;
|
||||
}
|
||||
|
||||
override public function set enabled (enable :Boolean) :void
|
||||
{
|
||||
super.enabled = enable;
|
||||
buttonMode = enable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the command and argument to be issued when this button is pressed.
|
||||
*/
|
||||
public function setCommand (cmd :String, arg :Object = null) :void
|
||||
{
|
||||
_cmdOrFn = cmd;
|
||||
_arg = arg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a callback function to call when the button is pressed.
|
||||
*/
|
||||
public function setCallback (fn :Function, arg :Object = null) :void
|
||||
{
|
||||
_cmdOrFn = fn;
|
||||
_arg = arg;
|
||||
}
|
||||
|
||||
override protected function clickHandler (event :MouseEvent) :void
|
||||
{
|
||||
super.clickHandler(event);
|
||||
CommandButton.processCommandClick(this, event, _cmdOrFn, _arg);
|
||||
}
|
||||
|
||||
/** The command (String) to submit, or the function (Function) to call
|
||||
* when clicked, */
|
||||
protected var _cmdOrFn :Object;
|
||||
|
||||
/** Any argument that accompanies our command. */
|
||||
protected var _arg :Object;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,537 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.flex {
|
||||
|
||||
import flash.display.DisplayObject;
|
||||
import flash.display.DisplayObjectContainer;
|
||||
|
||||
import flash.events.Event;
|
||||
import flash.events.IEventDispatcher;
|
||||
import flash.events.MouseEvent;
|
||||
|
||||
import flash.geom.Point;
|
||||
import flash.geom.Rectangle;
|
||||
|
||||
import mx.controls.Button;
|
||||
import mx.controls.Menu;
|
||||
import mx.controls.listClasses.BaseListData;
|
||||
import mx.controls.listClasses.IListItemRenderer;
|
||||
import mx.controls.menuClasses.IMenuItemRenderer;
|
||||
import mx.controls.menuClasses.MenuListData;
|
||||
|
||||
import mx.core.mx_internal;
|
||||
import mx.core.Application;
|
||||
import mx.core.ClassFactory;
|
||||
import mx.core.IFlexDisplayObject;
|
||||
import mx.core.ScrollPolicy;
|
||||
|
||||
import mx.events.MenuEvent;
|
||||
|
||||
import mx.managers.PopUpManager;
|
||||
|
||||
import com.threerings.util.CommandEvent;
|
||||
|
||||
import com.threerings.flex.PopUpUtil;
|
||||
|
||||
import com.threerings.flex.menuClasses.CommandMenuItemRenderer;
|
||||
import com.threerings.flex.menuClasses.CommandListData;
|
||||
|
||||
use namespace mx_internal;
|
||||
|
||||
/**
|
||||
* A pretty standard menu that can submit CommandEvents if menu items have "command" and possibly
|
||||
* "arg" properties. Commands are submitted to controllers for processing. Alternatively, you may
|
||||
* specify "callback" properties that specify a function closure to call, with the "arg" property
|
||||
* containing either a single arg or an array of args.
|
||||
*
|
||||
* Another property now supported is "iconObject", which is an already-instantiated
|
||||
* IFlexDisplayObject to use as the icon. This will only be applied if the "icon" property
|
||||
* (which specifies a Class) is not used.
|
||||
*
|
||||
* Another 'type' now supported is "title", which gives the label for that item the style
|
||||
* '.menuTitle' and disables it. Your ".menuTitle" style should use the disabledColor.
|
||||
*
|
||||
* Example dataProvider array:
|
||||
* [ { label: "Go home", icon: homeIconClass,
|
||||
* command: Controller.GO_HOME, arg: homeId },
|
||||
* { type: "separator"},
|
||||
{ label: "Crazytown", callback: setCrazy, arg: [ true, false ] },
|
||||
* { label: "Other places", children: subMenuArray }
|
||||
* ];
|
||||
*
|
||||
* See "Defining menu structure and data" in the Flex manual for the full list.
|
||||
*
|
||||
* CommandMenu is scrollable. This can be forced by setting verticalScrollPolicy to
|
||||
* ScrollPolicy.AUTO or ScrollPolicy.ON and setting the maxHeight. Also, if the scrolling isn't
|
||||
* forced on, but the menu does not fit within the vertical bounds given (either the stage size by
|
||||
* default, or the height of the rectangle given in setBounds()), scrolling will be turned on
|
||||
* so that none of the content is lost.
|
||||
*
|
||||
* Note: we don't extend flexlib's ScrollableMenu (or its sub-class ScrollableArrowMenu) because it
|
||||
* does some weird stuff in measure() that forces some of our menus down to a very small height...
|
||||
*/
|
||||
public class CommandMenu extends Menu
|
||||
{
|
||||
/**
|
||||
* Factory method to create a command menu.
|
||||
*
|
||||
* @param items an array of menu items.
|
||||
* @param dispatcher an override event dispatcher to use for command events, rather than
|
||||
* our parent.
|
||||
*/
|
||||
public static function createMenu (
|
||||
items :Array, dispatcher :IEventDispatcher = null) :CommandMenu
|
||||
{
|
||||
var menu :CommandMenu = new CommandMenu();
|
||||
menu.owner = DisplayObjectContainer(Application.application);
|
||||
menu.tabEnabled = false;
|
||||
menu.showRoot = true;
|
||||
menu.setDispatcher(dispatcher);
|
||||
Menu.popUpMenu(menu, null, items); // does not actually pop up, but needed.
|
||||
return menu;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a title to the TOP of the specified menu.
|
||||
*
|
||||
* @param icon may be a Class or IFlexDisplayObject.
|
||||
*/
|
||||
public static function addTitle (
|
||||
menuItems :Array, title :String, icon :* = null, separatorAfter :Boolean = true) :void
|
||||
{
|
||||
var o :Object = { label: title, type: "title" };
|
||||
if (icon is Class) {
|
||||
o["icon"] = icon;
|
||||
} else if (icon is IFlexDisplayObject) {
|
||||
o["iconObject"] = icon;
|
||||
}
|
||||
if (separatorAfter) {
|
||||
menuItems.unshift({ type: "separator" });
|
||||
}
|
||||
menuItems.unshift(o);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a separator to the specified menu, unless it would not make sense to do so, because
|
||||
* the menu is empty or already ends with a separator.
|
||||
*/
|
||||
public static function addSeparator (menuItems :Array) :void
|
||||
{
|
||||
const len :int = menuItems.length;
|
||||
if (len > 0 && menuItems[len - 1].type != "separator") {
|
||||
menuItems.push({ type: "separator" });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The mx.controls.Menu class overrides setting and getting the verticalScrollPolicy
|
||||
* basically setting the verticalScrollPolicy did nothing, and getting it always returned
|
||||
* ScrollPolicy.OFF. So that's not going to work if we want the menu to scroll. Here we
|
||||
* reinstate the verticalScrollPolicy setter, and keep a local copy of the value in a
|
||||
* protected variable _verticalScrollPolicy.
|
||||
*
|
||||
* This setter is basically a copy of what ScrollControlBase and ListBase do.
|
||||
*/
|
||||
override public function set verticalScrollPolicy (value :String) :void
|
||||
{
|
||||
var newPolicy :String = value.toLowerCase();
|
||||
|
||||
itemsSizeChanged = true;
|
||||
|
||||
if (_verticalScrollPolicy != newPolicy) {
|
||||
_verticalScrollPolicy = newPolicy;
|
||||
dispatchEvent(new Event("verticalScrollPolicyChanged"));
|
||||
}
|
||||
|
||||
invalidateDisplayList();
|
||||
}
|
||||
|
||||
override public function get verticalScrollPolicy () :String
|
||||
{
|
||||
return _verticalScrollPolicy;
|
||||
}
|
||||
|
||||
public function CommandMenu ()
|
||||
{
|
||||
super();
|
||||
|
||||
itemRenderer = new ClassFactory(getItemRenderer());
|
||||
|
||||
verticalScrollPolicy = ScrollPolicy.OFF;
|
||||
variableRowHeight = true;
|
||||
addEventListener(MenuEvent.ITEM_CLICK, itemClicked);
|
||||
addEventListener(MenuEvent.MENU_HIDE, menuHidden);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called in the CommandMenu constructor, this should return the item rendering class for this
|
||||
* CommandMenu.
|
||||
*/
|
||||
protected function getItemRenderer () :Class
|
||||
{
|
||||
return CommandMenuItemRenderer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the button that popped-up this menu, which enables two features:
|
||||
* 1) If the button is clicked again while the menu is up, it won't re-trigger, the menu will
|
||||
* just close.
|
||||
* 2) However the menu closes, if the button is a toggle, it will be de-selected.
|
||||
*/
|
||||
public function setTriggerButton (trigger :Button) :void
|
||||
{
|
||||
_trigger = trigger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the event dispatcher to be used when dispatching this menu's command events.
|
||||
* Normally they will be dispatched on our parent (usually the SystemManager or something).
|
||||
*/
|
||||
public function setDispatcher (dispatcher :IEventDispatcher) :void
|
||||
{
|
||||
_dispatcher = dispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a Rectangle (in stage coords) that the menu will attempt to keep submenus positioned
|
||||
* within. If a submenu is too large to fit, it will position it in the lower right corner
|
||||
* of this Rectangle (or top if popping upwards, or left if popping leftwards). This
|
||||
* value defaults to the stage bounds.
|
||||
*/
|
||||
public function setBounds (fitting :Rectangle) :void
|
||||
{
|
||||
_fitting = fitting;
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually pop up the menu. This can be used instead of show().
|
||||
*/
|
||||
public function popUp (
|
||||
trigger :DisplayObject, popUpwards :Boolean = false, popLeftwards :Boolean = false) :void
|
||||
{
|
||||
_upping = popUpwards;
|
||||
_lefting = popLeftwards;
|
||||
var r :Rectangle = trigger.getBounds(trigger.stage);
|
||||
show(_lefting ? r.left : r.right, _upping ? r.top : r.bottom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the menu at the specified mouse coordinates.
|
||||
*/
|
||||
public function popUpAt (
|
||||
mx :int, my :int, popUpwards :Boolean = false, popLeftwards :Boolean = false) :void
|
||||
{
|
||||
_upping = popUpwards;
|
||||
_lefting = popLeftwards;
|
||||
show(mx, my);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the menu at the current mouse location.
|
||||
*/
|
||||
public function popUpAtMouse (popUpwards :Boolean = false, popLeftwards :Boolean = false) :void
|
||||
{
|
||||
_upping = popUpwards;
|
||||
_lefting = popLeftwards;
|
||||
show(); // our show, with no args, pops at the mouse
|
||||
}
|
||||
|
||||
/**
|
||||
* Just like our superclass's show(), except that when invoked with no args, causes the menu to
|
||||
* show at the current mouse location instead of the top-left corner of the application.
|
||||
* Also, we ensure that the resulting menu is in-bounds.
|
||||
*/
|
||||
override public function show (xShow :Object = null, yShow :Object = null) :void
|
||||
{
|
||||
if (xShow == null) {
|
||||
xShow = DisplayObject(Application.application).mouseX;
|
||||
}
|
||||
if (yShow == null) {
|
||||
yShow = DisplayObject(Application.application).mouseY;
|
||||
}
|
||||
|
||||
super.show(xShow, yShow);
|
||||
|
||||
// reposition now that we know our size
|
||||
if (_lefting) {
|
||||
y = x - getExplicitOrMeasuredWidth();
|
||||
}
|
||||
if (_upping) {
|
||||
y = y - getExplicitOrMeasuredHeight();
|
||||
}
|
||||
|
||||
var fitting :Rectangle = _fitting || screen;
|
||||
PopUpUtil.fitInRect(this, fitting);
|
||||
|
||||
// if after fitting as best we can, the menu is outside of the declared bounds, we force
|
||||
// scrolling and set the max height
|
||||
if (y < fitting.y) {
|
||||
verticalScrollPolicy = ScrollPolicy.AUTO;
|
||||
maxHeight = fitting.height;
|
||||
y = fitting.y;
|
||||
}
|
||||
|
||||
// set up some stuff to clean up and behave
|
||||
systemManager.topLevelSystemManager.getSandboxRoot().addEventListener(
|
||||
MouseEvent.MOUSE_DOWN, handleMouseDownOutside, false, 1, true);
|
||||
}
|
||||
|
||||
override public function hide () :void
|
||||
{
|
||||
super.hide();
|
||||
systemManager.topLevelSystemManager.getSandboxRoot().removeEventListener(
|
||||
MouseEvent.MOUSE_DOWN, handleMouseDownOutside);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Menu class overrode configureScrollBars() and made the function do nothing. That means
|
||||
* the scrollbars don't know how to draw themselves, so here we reinstate configureScrollBars.
|
||||
* This is basically a copy of the same method from the mx.controls.List class.
|
||||
*/
|
||||
override protected function configureScrollBars () :void
|
||||
{
|
||||
var rowCount :int = listItems.length;
|
||||
if (rowCount == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if there is more than one row and it is a partial row we don't count it
|
||||
if (rowCount > 1 &&
|
||||
rowInfo[rowCount - 1].y + rowInfo[rowCount - 1].height > listContent.height) {
|
||||
rowCount--;
|
||||
}
|
||||
|
||||
// offset, when added to rowCount, is hte index of the dataProvider item for that row.
|
||||
// IOW, row 10 in listItems is showing dataProvider item 10 + verticalScrollPosition -
|
||||
// lockedRowCount - 1
|
||||
var offset :int = verticalScrollPosition - lockedRowCount - 1;
|
||||
|
||||
// don't count filler rows at the bottom either.
|
||||
var fillerRows :int = 0;
|
||||
while (rowCount > 0 && listItems[rowCount - 1].length == 0)
|
||||
{
|
||||
if (collection && rowCount + offset >= collection.length) {
|
||||
rowCount--;
|
||||
fillerRows++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var colCount :int = listItems[0].length;
|
||||
var oldHorizontalScrollBar :Object = horizontalScrollBar;
|
||||
var oldVerticalScrollBar :Object = verticalScrollBar;
|
||||
var roundedWidth :int = Math.round(unscaledWidth);
|
||||
var length :int = collection ? collection.length - lockedRowCount : 0;
|
||||
var numRows :int = rowCount - lockedRowCount;
|
||||
|
||||
setScrollBarProperties(Math.round(listContent.width), roundedWidth, length, numRows);
|
||||
maxVerticalScrollPosition = Math.max(length - numRows, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for MenuEvent.ITEM_CLICK.
|
||||
*/
|
||||
protected function itemClicked (event :MenuEvent) :void
|
||||
{
|
||||
var arg :Object = getItemProp(event.item, "arg");
|
||||
var cmdOrFn :Object = getItemProp(event.item, "command");
|
||||
if (cmdOrFn == null) {
|
||||
cmdOrFn = getItemProp(event.item, "callback");
|
||||
}
|
||||
if (cmdOrFn != null) {
|
||||
event.stopImmediatePropagation();
|
||||
CommandEvent.dispatch(_dispatcher == null ? mx_internal::parentDisplayObject :
|
||||
_dispatcher, cmdOrFn, arg);
|
||||
}
|
||||
// else: no warning. There may be non-command menu items mixed in.
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles MenuEvent.MENU_HIDE.
|
||||
*/
|
||||
protected function menuHidden (event :MenuEvent) :void
|
||||
{
|
||||
// don't react to submenu hides
|
||||
if ((event.menu == this) && (_trigger != null) && _trigger.toggle) {
|
||||
_trigger.selected = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a down-popping click outside the menu.
|
||||
*/
|
||||
protected function handleMouseDownOutside (event :MouseEvent) :void
|
||||
{
|
||||
if (event.target == _trigger) {
|
||||
_trigger.addEventListener(
|
||||
MouseEvent.CLICK, handleTriggerClick, false, int.MAX_VALUE, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Suppress the click that wants to land on the trigger button.
|
||||
*/
|
||||
protected function handleTriggerClick (event :MouseEvent) :void
|
||||
{
|
||||
event.stopImmediatePropagation();
|
||||
_trigger.removeEventListener(MouseEvent.CLICK, handleTriggerClick);
|
||||
}
|
||||
|
||||
// from ScrollableArrowMenu..
|
||||
override mx_internal function openSubMenu (row :IListItemRenderer) :void
|
||||
{
|
||||
supposedToLoseFocus = true;
|
||||
|
||||
var r :Menu = getRootMenu();
|
||||
var menu :CommandMenu;
|
||||
|
||||
// check to see if the menu exists, if not create it
|
||||
if (!IMenuItemRenderer(row).menu) {
|
||||
// the only differences between this method and the original method in mx.controls.Menu
|
||||
// are these few lines.
|
||||
menu = new CommandMenu();
|
||||
menu.maxHeight = this.maxHeight;
|
||||
menu.verticalScrollPolicy = this.verticalScrollPolicy;
|
||||
menu.variableRowHeight = this.variableRowHeight;
|
||||
|
||||
menu.parentMenu = this;
|
||||
menu.owner = this;
|
||||
menu.showRoot = showRoot;
|
||||
menu.dataDescriptor = r.dataDescriptor;
|
||||
menu.styleName = r;
|
||||
menu.labelField = r.labelField;
|
||||
menu.labelFunction = r.labelFunction;
|
||||
menu.iconField = r.iconField;
|
||||
menu.iconFunction = r.iconFunction;
|
||||
menu.itemRenderer = r.itemRenderer;
|
||||
menu.rowHeight = r.rowHeight;
|
||||
menu.scaleY = r.scaleY;
|
||||
menu.scaleX = r.scaleX;
|
||||
|
||||
// if there's data and it has children then add the items
|
||||
if (row.data && _dataDescriptor.isBranch(row.data) &&
|
||||
_dataDescriptor.hasChildren(row.data)) {
|
||||
menu.dataProvider = _dataDescriptor.getChildren(row.data);
|
||||
}
|
||||
menu.sourceMenuBar = sourceMenuBar;
|
||||
menu.sourceMenuBarItem = sourceMenuBarItem;
|
||||
|
||||
IMenuItemRenderer(row).menu = menu;
|
||||
PopUpManager.addPopUp(menu, r, false);
|
||||
}
|
||||
|
||||
super.openSubMenu(row);
|
||||
|
||||
// if we're lefting, upping or fitting make sure our submenu does so as well
|
||||
var submenu :Menu = IMenuItemRenderer(row).menu;
|
||||
if (_lefting) {
|
||||
submenu.x -= submenu.getExplicitOrMeasuredWidth();
|
||||
}
|
||||
if (_upping) {
|
||||
var rowLoc :Point = row.localToGlobal(new Point());
|
||||
submenu.y = rowLoc.y - submenu.getExplicitOrMeasuredHeight() + row.height;
|
||||
}
|
||||
|
||||
var fitting :Rectangle = _fitting || screen;
|
||||
PopUpUtil.fitInRect(submenu, fitting);
|
||||
// if after fitting as best we can, the menu is outside of the declared bounds, we force
|
||||
// scrolling and set the max height
|
||||
if (submenu.y < fitting.y) {
|
||||
submenu.verticalScrollPolicy = ScrollPolicy.AUTO;
|
||||
submenu.maxHeight = fitting.height;
|
||||
submenu.y = fitting.y;
|
||||
}
|
||||
}
|
||||
|
||||
override protected function measure () :void
|
||||
{
|
||||
super.measure();
|
||||
|
||||
if (measuredHeight > this.maxHeight) {
|
||||
measuredHeight = this.maxHeight;
|
||||
}
|
||||
|
||||
if (verticalScrollPolicy == ScrollPolicy.ON || verticalScrollPolicy == ScrollPolicy.AUTO) {
|
||||
if (verticalScrollBar) {
|
||||
measuredMinWidth = measuredWidth = measuredWidth + verticalScrollBar.minWidth;
|
||||
}
|
||||
}
|
||||
|
||||
commitProperties();
|
||||
}
|
||||
|
||||
// from List
|
||||
override protected function makeListData (data :Object, uid :String, rowNum :int) :BaseListData
|
||||
{
|
||||
// Oh, FFS.
|
||||
// We need to set up these "maxMeasuredIconWidth" fields on the MenuListData, but our
|
||||
// superclass has made those variables private.
|
||||
// We can get the values out of another MenuListData, so we just always call super()
|
||||
// to create one of those, and if we need to make a CommandListData, we construct one
|
||||
// from the fields in the MenuListData.
|
||||
|
||||
var menuListData :MenuListData = super.makeListData(data, uid, rowNum) as MenuListData;
|
||||
|
||||
var iconObject :IFlexDisplayObject = getItemProp(data, "iconObject") as IFlexDisplayObject;
|
||||
if (iconObject != null) {
|
||||
var cmdListData :CommandListData = new CommandListData(menuListData.label,
|
||||
menuListData.icon, iconObject, labelField, uid, this, rowNum);
|
||||
|
||||
cmdListData.maxMeasuredIconWidth = menuListData.maxMeasuredIconWidth;
|
||||
cmdListData.maxMeasuredTypeIconWidth = menuListData.maxMeasuredTypeIconWidth;
|
||||
cmdListData.maxMeasuredBranchIconWidth = menuListData.maxMeasuredBranchIconWidth;
|
||||
cmdListData.useTwoColumns = menuListData.useTwoColumns;
|
||||
return cmdListData;
|
||||
}
|
||||
|
||||
return menuListData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the specified property for the specified item, if any. Somewhat similar to bits in the
|
||||
* DefaultDataDescriptor.
|
||||
*/
|
||||
protected function getItemProp (item :Object, prop :String) :Object
|
||||
{
|
||||
try {
|
||||
if (item is XML) {
|
||||
return String((item as XML).attribute(prop));
|
||||
|
||||
} else if (prop in item) {
|
||||
return item[prop];
|
||||
}
|
||||
|
||||
} catch (e :Error) {
|
||||
// alas; fall through
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected var _dispatcher :IEventDispatcher;
|
||||
protected var _trigger :Button;
|
||||
protected var _lefting :Boolean = false;
|
||||
protected var _upping :Boolean = false;
|
||||
protected var _fitting :Rectangle = null;
|
||||
protected var _verticalScrollPolicy :String;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.flex {
|
||||
|
||||
import flash.display.DisplayObject;
|
||||
import flash.display.InteractiveObject;
|
||||
import flash.display.Sprite;
|
||||
import flash.events.MouseEvent;
|
||||
import flash.text.TextField;
|
||||
import flash.text.TextFieldType;
|
||||
import flash.ui.Mouse;
|
||||
|
||||
import mx.core.Application;
|
||||
import mx.managers.SystemManager;
|
||||
import mx.styles.CSSStyleDeclaration;
|
||||
import mx.styles.StyleManager;
|
||||
|
||||
import com.threerings.util.Map;
|
||||
import com.threerings.util.Maps;
|
||||
|
||||
/**
|
||||
* A better CursorManager.
|
||||
*/
|
||||
public class CursorManager
|
||||
{
|
||||
public static const SYSTEM_CURSOR :int = 0;
|
||||
|
||||
public static const BUSY_CURSOR :int = 1;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
//
|
||||
// Class methods
|
||||
//
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Makes the cursor visible.
|
||||
* Cursor visibility is not reference-counted.
|
||||
* A single call to the <code>showCursor()</code> method
|
||||
* always shows the cursor regardless of how many calls
|
||||
* to the <code>hideCursor()</code> method were made.
|
||||
*/
|
||||
public static function showCursor () :void
|
||||
{
|
||||
if (_currentCursorId == SYSTEM_CURSOR) {
|
||||
Mouse.show();
|
||||
} else {
|
||||
_cursorHolder.visible = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the cursor invisible.
|
||||
* Cursor visibility is not reference-counted.
|
||||
* A single call to the <code>hideCursor()</code> method
|
||||
* always hides the cursor regardless of how many calls
|
||||
* to the <code>showCursor()</code> method were made.
|
||||
*/
|
||||
public static function hideCursor () :void
|
||||
{
|
||||
_cursorHolder.visible = false;
|
||||
Mouse.hide();
|
||||
}
|
||||
|
||||
public static function getCurrentCursorId () :int
|
||||
{
|
||||
return _currentCursorId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new cursor.
|
||||
*
|
||||
* @param cursorClass Class of the cursor to display.
|
||||
*
|
||||
* @param xOffset Number that specifies the x offset
|
||||
* of the cursor, in pixels, relative to the mouse pointer.
|
||||
* The default value is 0.
|
||||
*
|
||||
* @param yOffset Number that specifies the y offset
|
||||
* of the cursor, in pixels, relative to the mouse pointer.
|
||||
* The default value is 0.
|
||||
*
|
||||
* @return The ID of the cursor.
|
||||
*/
|
||||
public static function addCursor (
|
||||
cursorClass :Class, xOffset :int = 0, yOffset :int = 0) :int
|
||||
{
|
||||
var cursorId :int = _nextCursorId++;
|
||||
|
||||
var rec :CursorRecord = new CursorRecord(cursorClass);
|
||||
rec.x = xOffset;
|
||||
rec.y = yOffset;
|
||||
|
||||
_cursors.put(cursorId, rec);
|
||||
|
||||
return cursorId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current cursor.
|
||||
*/
|
||||
public static function setCursor (id :int) :void
|
||||
{
|
||||
if (id == _currentCursorId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_initialized) {
|
||||
// oh, let's set it up
|
||||
_systemManager = Application.application.systemManager;
|
||||
|
||||
// set up the busy cursor
|
||||
var cursorManagerStyleDeclaration :CSSStyleDeclaration =
|
||||
StyleManager.getStyleDeclaration("CursorManager");
|
||||
var busyCursorClass :Class =
|
||||
cursorManagerStyleDeclaration.getStyle("busyCursor");
|
||||
_busyCursor = new CursorRecord(busyCursorClass);
|
||||
|
||||
// The first time a cursor is requested of the CursorManager,
|
||||
// create a Sprite to hold the cursor symbol
|
||||
_cursorHolder = new Sprite();
|
||||
_cursorHolder.mouseEnabled = false;
|
||||
_systemManager.cursorChildren.addChild(_cursorHolder);
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
// figure out what the new cursor will be like
|
||||
var rec :CursorRecord;
|
||||
if (id == BUSY_CURSOR) {
|
||||
rec = _busyCursor;
|
||||
|
||||
} else if (id != SYSTEM_CURSOR) {
|
||||
rec = (_cursors.get(id) as CursorRecord);
|
||||
if (rec == null) {
|
||||
// a bogus id was specified
|
||||
// TODO: throw an error?
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (rec != null && (rec.cursor is Class)) {
|
||||
// go ahead and instantiate the class
|
||||
try {
|
||||
var disp :DisplayObject = new (rec.cursor as Class);
|
||||
rec.cursor = disp;
|
||||
if (disp is InteractiveObject) {
|
||||
(disp as InteractiveObject).mouseEnabled = false;
|
||||
}
|
||||
|
||||
} catch (err :Error) {
|
||||
// this cursor is not usable, bail
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// always remove any custom cursors from the hierarchy
|
||||
if (_cursorHolder.numChildren > 0) {
|
||||
_cursorHolder.removeChildAt(0);
|
||||
}
|
||||
|
||||
if (id != SYSTEM_CURSOR) {
|
||||
Mouse.hide();
|
||||
var currentCursor :DisplayObject = (rec.cursor as DisplayObject);
|
||||
_cursorHolder.addChild(currentCursor);
|
||||
_cursorHolder.x = _systemManager.mouseX + rec.x;
|
||||
_cursorHolder.y = _systemManager.mouseY + rec.y;
|
||||
|
||||
_currentXOffset = rec.x;
|
||||
_currentYOffset = rec.y;
|
||||
_systemManager.stage.addEventListener(
|
||||
MouseEvent.MOUSE_MOVE, mouseMoveHandler);
|
||||
|
||||
|
||||
} else {
|
||||
_currentXOffset = 0;
|
||||
_currentYOffset = 0;
|
||||
_systemManager.stage.removeEventListener(
|
||||
MouseEvent.MOUSE_MOVE, mouseMoveHandler);
|
||||
|
||||
Mouse.show();
|
||||
}
|
||||
|
||||
_currentCursorId = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a cursor from the cursor list.
|
||||
* If the cursor being removed is the currently displayed cursor,
|
||||
* the CursorManager displays the next cursor in the list, if one exists.
|
||||
* If the list becomes empty, the CursorManager displays
|
||||
* the default system cursor.
|
||||
*
|
||||
* @param cursorID ID of cursor to remove.
|
||||
*/
|
||||
public static function removeCursor (id :int) :void
|
||||
{
|
||||
var rec :CursorRecord = (_cursors.remove(id) as CursorRecord);
|
||||
|
||||
if (rec != null && id == _currentCursorId) {
|
||||
setCursor(SYSTEM_CURSOR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all of the cursors from the cursor list
|
||||
* and restores the system cursor.
|
||||
*/
|
||||
public static function removeAllCursors () :void
|
||||
{
|
||||
_cursors.clear();
|
||||
|
||||
if (_currentCursorId != BUSY_CURSOR) {
|
||||
setCursor(SYSTEM_CURSOR);
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
//
|
||||
// Class event handlers
|
||||
//
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
private static function mouseMoveHandler (event :MouseEvent) :void
|
||||
{
|
||||
_cursorHolder.x = _systemManager.mouseX + _currentXOffset;
|
||||
_cursorHolder.y = _systemManager.mouseY + _currentYOffset;
|
||||
|
||||
var target :Object = event.target;
|
||||
|
||||
// Do target test.
|
||||
if (!_overTextField &&
|
||||
target is TextField && target.type == TextFieldType.INPUT) {
|
||||
_overTextField = true;
|
||||
_showSystemCursor = true;
|
||||
|
||||
} else if (_overTextField &&
|
||||
!(target is TextField && target.type == TextFieldType.INPUT)) {
|
||||
_overTextField = false;
|
||||
_showCustomCursor = true;
|
||||
}
|
||||
|
||||
// Handle switching between system and custom cursor.
|
||||
if (_showSystemCursor) {
|
||||
_showSystemCursor = false;
|
||||
_cursorHolder.visible = false;
|
||||
Mouse.show();
|
||||
}
|
||||
if (_showCustomCursor) {
|
||||
_showCustomCursor = false;
|
||||
_cursorHolder.visible = true;
|
||||
Mouse.hide();
|
||||
}
|
||||
}
|
||||
|
||||
/** A mapping of all assigned cursor ids. */
|
||||
private static const _cursors :Map = Maps.newMapOf(int);
|
||||
|
||||
private static var _currentCursorId :int = 0; // SYSTEM_CURSOR
|
||||
|
||||
private static var _nextCursorId :int = 2; // skip BUSY
|
||||
|
||||
private static var _initialized :Boolean = false;
|
||||
|
||||
private static var _cursorHolder :Sprite;
|
||||
|
||||
private static var _currentXOffset :int = 0;
|
||||
private static var _currentYOffset :int = 0;
|
||||
|
||||
/** A record for the busy cursor (where it can't be removed). */
|
||||
private static var _busyCursor :CursorRecord;
|
||||
|
||||
private static var _overTextField :Boolean = false;
|
||||
|
||||
private static var _overLink :Boolean = false;
|
||||
|
||||
private static var _showSystemCursor :Boolean = false;
|
||||
|
||||
private static var _showCustomCursor :Boolean = false;
|
||||
|
||||
private static var _systemManager :SystemManager = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
class CursorRecord extends Object
|
||||
{
|
||||
public function CursorRecord (clazz :Class)
|
||||
{
|
||||
cursor = clazz;
|
||||
}
|
||||
|
||||
/** The class, or instantiated cursor. */
|
||||
public var cursor :Object
|
||||
|
||||
/** The x/y offset for the hotspot. */
|
||||
public var x :Number;
|
||||
public var y :Number;
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.flex {
|
||||
|
||||
import mx.core.IFactory;
|
||||
import mx.core.ScrollPolicy;
|
||||
|
||||
import mx.collections.ArrayCollection;
|
||||
import mx.collections.IList;
|
||||
import mx.collections.Sort;
|
||||
|
||||
import mx.controls.List;
|
||||
|
||||
import com.threerings.util.Util;
|
||||
|
||||
import com.threerings.presents.dobj.AttributeChangedEvent;
|
||||
import com.threerings.presents.dobj.DEvent;
|
||||
import com.threerings.presents.dobj.DObject;
|
||||
import com.threerings.presents.dobj.DSet;
|
||||
import com.threerings.presents.dobj.DSet_Entry;
|
||||
import com.threerings.presents.dobj.EntryAddedEvent;
|
||||
import com.threerings.presents.dobj.EntryUpdatedEvent;
|
||||
import com.threerings.presents.dobj.EntryRemovedEvent;
|
||||
import com.threerings.presents.dobj.EventListener;
|
||||
import com.threerings.presents.dobj.NamedEvent;
|
||||
|
||||
/**
|
||||
* A list that renders the contents of a DSet.
|
||||
*/
|
||||
public class DSetList extends List
|
||||
implements EventListener
|
||||
{
|
||||
public function DSetList (
|
||||
renderer :IFactory, sortFn :Function = null, filterFn :Function = null)
|
||||
{
|
||||
horizontalScrollPolicy = ScrollPolicy.OFF;
|
||||
verticalScrollPolicy = ScrollPolicy.ON;
|
||||
selectable = false;
|
||||
|
||||
itemRenderer = renderer;
|
||||
|
||||
_data = new ArrayCollection();
|
||||
var sort :Sort = new Sort();
|
||||
sort.compareFunction = sortFn;
|
||||
_data.sort = sort;
|
||||
_data.filterFunction = filterFn;
|
||||
_data.refresh();
|
||||
dataProvider = _data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the data to be displayed by this List, if not being attached to a DObject.
|
||||
*/
|
||||
public function setData (array :Array /* of DSet_Entry */) :void
|
||||
{
|
||||
_data.source = (array == null) ? [] : array;
|
||||
refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the data, which can be called if something external has changed
|
||||
* the sort order or the rendering.
|
||||
*/
|
||||
public function refresh () :void
|
||||
{
|
||||
_data.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll so that the entry with the specified key is visible.
|
||||
*/
|
||||
public function scrollToKey (key :Object) :Boolean
|
||||
{
|
||||
var scrollIdx :int = findKeyIndex(key, false);
|
||||
return (scrollIdx == -1) ? false : scrollToIndex(scrollIdx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize listening on the specified object.
|
||||
*
|
||||
* @param object the DObject on which to listen.
|
||||
* @param field the name of the DSet field that we're displaying.
|
||||
* @param refreshFields additional fields that cause refresh() to be called should
|
||||
* AttributeChangedEvents arrive on them.
|
||||
*/
|
||||
public function init (object :DObject, field :String, ... refreshFields) :void
|
||||
{
|
||||
shutdown();
|
||||
|
||||
_object = object;
|
||||
_field = field;
|
||||
_refreshFields = refreshFields;
|
||||
_object.addListener(this);
|
||||
|
||||
setEntries(_object[_field] as DSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shut down listening.
|
||||
*/
|
||||
public function shutdown () :void
|
||||
{
|
||||
if (_object != null) {
|
||||
_object.removeListener(this);
|
||||
_object = null;
|
||||
_field = null;
|
||||
_refreshFields = null;
|
||||
setData(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely set the entries, even if null.
|
||||
*/
|
||||
protected function setEntries (entries :DSet) :void
|
||||
{
|
||||
setData((entries == null) ? null : entries.toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* From com.threerings.presents.dobj.EventListener. Not actually part of the public API.
|
||||
*/
|
||||
public function eventReceived (event :DEvent) :void
|
||||
{
|
||||
if (!(event is NamedEvent)) {
|
||||
return;
|
||||
}
|
||||
var name :String = NamedEvent(event).getName();
|
||||
if (name == _field) {
|
||||
if (event is EntryAddedEvent) {
|
||||
_data.list.addItem(EntryAddedEvent(event).getEntry());
|
||||
|
||||
} else if (event is EntryUpdatedEvent) {
|
||||
var entry :DSet_Entry = EntryUpdatedEvent(event).getEntry();
|
||||
var upIdx :int = findKeyIndex(entry.getKey());
|
||||
if (upIdx != -1) {
|
||||
_data.list.setItemAt(entry, upIdx);
|
||||
}
|
||||
|
||||
} else if (event is EntryRemovedEvent) {
|
||||
var remIdx :int = findKeyIndex(EntryRemovedEvent(event).getKey());
|
||||
if (remIdx != -1) {
|
||||
_data.list.removeItemAt(remIdx);
|
||||
}
|
||||
|
||||
} else if (event is AttributeChangedEvent) {
|
||||
setEntries(AttributeChangedEvent(event).getValue() as DSet);
|
||||
return; // skip the full refresh
|
||||
}
|
||||
|
||||
} else if (-1 == _refreshFields.indexOf(name)) {
|
||||
return; // this name is not our concern: do nothing
|
||||
}
|
||||
|
||||
// we reach this line if the event was for a _refreshField, or a Set event on _field
|
||||
refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the index of the specified entry key from the raw or filtered list.
|
||||
*/
|
||||
protected function findKeyIndex (key :Object, raw :Boolean = true) :int
|
||||
{
|
||||
var list :IList = raw ? _data.list : _data;
|
||||
for (var ii :int = 0; ii < list.length; ii++) {
|
||||
if (Util.equals(key, DSet_Entry(list.getItemAt(ii)).getKey())) {
|
||||
return ii;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** The collection for the List, This is the filtered/sorted view. For the
|
||||
* the raw list, we access _data.list. */
|
||||
protected var _data :ArrayCollection;
|
||||
|
||||
/** The object on which we're listening. */
|
||||
protected var _object :DObject;
|
||||
|
||||
/** The fieldname of the DSet in the _object. */
|
||||
protected var _field :String;
|
||||
|
||||
/** Fields on which to watch for updates and call refresh(). */
|
||||
protected var _refreshFields :Array;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.flex {
|
||||
|
||||
import flash.display.DisplayObject;
|
||||
|
||||
import mx.controls.Label;
|
||||
import mx.controls.Spacer;
|
||||
import mx.controls.Text;
|
||||
|
||||
import mx.core.Container;
|
||||
import mx.core.UIComponent;
|
||||
|
||||
import mx.containers.HBox;
|
||||
import mx.containers.VBox;
|
||||
|
||||
/**
|
||||
* Flex-related utility methods.
|
||||
*/
|
||||
public class FlexUtil
|
||||
{
|
||||
/**
|
||||
* Creates a label with the supplied text, and optionally applies a style class and tooltip
|
||||
* to it.
|
||||
*/
|
||||
public static function createLabel (
|
||||
text :String, style :String = null, toolTip :String = null) :Label
|
||||
{
|
||||
var label :Label = new Label();
|
||||
label.text = text;
|
||||
label.styleName = style;
|
||||
label.toolTip = toolTip;
|
||||
return label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an uneditable/unselectable multiline Text widget with the specified text and width.
|
||||
*/
|
||||
public static function createText (text :String, width :int, style :String = null) :Text
|
||||
{
|
||||
var t :Text = new Text();
|
||||
t.styleName = style;
|
||||
t.width = width;
|
||||
t.selectable = false;
|
||||
t.text = text;
|
||||
return t;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an uneditable/unselectable multiline Text widget with 100% width.
|
||||
*/
|
||||
public static function createWideText (text :String, style :String = null) :Text
|
||||
{
|
||||
var t :Text = new Text();
|
||||
t.styleName = style;
|
||||
t.percentWidth = 100;
|
||||
t.selectable = false;
|
||||
t.text = text;
|
||||
return t;
|
||||
}
|
||||
|
||||
/**
|
||||
* How hard would it have been for them make Spacer accept two optional arguments?
|
||||
*/
|
||||
public static function createSpacer (width :int = 0, height :int = 0) :Spacer
|
||||
{
|
||||
var spacer :Spacer = new Spacer();
|
||||
spacer.width = width;
|
||||
spacer.height = height;
|
||||
return spacer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a count of how many visible (includeInLayout=true) children
|
||||
* are in the specified container.
|
||||
*/
|
||||
public static function countLayoutChildren (container :Container) :int
|
||||
{
|
||||
var layoutChildren :int = container.numChildren;
|
||||
for each (var child :Object in container.getChildren()) {
|
||||
if ((child is UIComponent) && !UIComponent(child).includeInLayout) {
|
||||
layoutChildren--;
|
||||
}
|
||||
}
|
||||
return layoutChildren;
|
||||
}
|
||||
|
||||
/**
|
||||
* In flex the 'visible' property controls visibility separate from whether
|
||||
* the component takes up space in the layout, which is controlled by 'includeInLayout'.
|
||||
* We usually want to set them together, so this does that for us.
|
||||
* @return the new visible setting.
|
||||
*/
|
||||
public static function setVisible (component :UIComponent, visible :Boolean) :Boolean
|
||||
{
|
||||
component.visible = visible;
|
||||
component.includeInLayout = visible;
|
||||
return visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new container of the given class and adds the given children to it.
|
||||
*/
|
||||
public static function createContainer (clazz :Class, ...children) :Container
|
||||
{
|
||||
var container :Container = new clazz() as Container;
|
||||
for each (var comp :UIComponent in children) {
|
||||
container.addChild(comp);
|
||||
}
|
||||
return container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new HBox container and adds the given children to it.
|
||||
*/
|
||||
public static function createHBox (...children) :HBox
|
||||
{
|
||||
children.unshift(HBox);
|
||||
return createContainer.apply(null, children) as HBox;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new VBox container and adds the given children to it.
|
||||
*/
|
||||
public static function createVBox (...children) :VBox
|
||||
{
|
||||
children.unshift(VBox);
|
||||
return createContainer.apply(null, children) as VBox;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a simple UIComponent that wraps a display object.
|
||||
*/
|
||||
public static function wrap (obj :DisplayObject) :FlexWrapper
|
||||
{
|
||||
return new FlexWrapper(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a simple UIComponent that wraps a display object and inherits its size.
|
||||
*/
|
||||
public static function wrapSized (obj :DisplayObject) :FlexWrapper
|
||||
{
|
||||
return new FlexWrapper(obj, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.flex {
|
||||
|
||||
import flash.display.DisplayObject;
|
||||
|
||||
import mx.core.UIComponent;
|
||||
|
||||
/**
|
||||
* Wraps a non-Flex component for use in Flex.
|
||||
*/
|
||||
public class FlexWrapper extends UIComponent
|
||||
{
|
||||
public function FlexWrapper (object :DisplayObject, inheritSize :Boolean = false)
|
||||
{
|
||||
// don't capture mouse events in this wrapper
|
||||
mouseEnabled = false;
|
||||
addChild(object);
|
||||
if (inheritSize) {
|
||||
width = object.width;
|
||||
height = object.height;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.flex {
|
||||
|
||||
import mx.effects.IEffectInstance;
|
||||
import mx.effects.Effect;
|
||||
|
||||
public class FunctionEffect extends Effect
|
||||
{
|
||||
/** The function to call. */
|
||||
public var func :Function;
|
||||
|
||||
/** The arguments to pass to the function. */
|
||||
public var args :Array;
|
||||
|
||||
public function FunctionEffect (target :Object = null)
|
||||
{
|
||||
super(target);
|
||||
|
||||
instanceClass = FunctionEffectInstance;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
override protected function initInstance (instance :IEffectInstance) :void
|
||||
{
|
||||
super.initInstance(instance);
|
||||
|
||||
var fe :FunctionEffectInstance = (instance as FunctionEffectInstance);
|
||||
fe.func = func;
|
||||
fe.args = args;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.flex {
|
||||
|
||||
import mx.effects.effectClasses.ActionEffectInstance;
|
||||
|
||||
public class FunctionEffectInstance extends ActionEffectInstance
|
||||
{
|
||||
public function FunctionEffectInstance (target :Object)
|
||||
{
|
||||
super(target);
|
||||
}
|
||||
|
||||
/** The function to call. */
|
||||
public var func :Function;
|
||||
|
||||
/** The args to pass. */
|
||||
public var args :Array;
|
||||
|
||||
override public function play () :void
|
||||
{
|
||||
super.play();
|
||||
|
||||
// call the function!
|
||||
func.apply(null, args);
|
||||
|
||||
// and we're done
|
||||
finishRepeat();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.flex {
|
||||
|
||||
import mx.containers.Grid;
|
||||
import mx.containers.GridItem;
|
||||
import mx.containers.GridRow;
|
||||
|
||||
import mx.controls.Label;
|
||||
|
||||
import mx.core.UIComponent;
|
||||
|
||||
/**
|
||||
* Convenience methods for adding children to Grids.
|
||||
*/
|
||||
public class GridUtil
|
||||
{
|
||||
/**
|
||||
* Add a new row to the grid, containing the specified
|
||||
* components.
|
||||
*
|
||||
* @param specs a list of components or Strings, or you can follow
|
||||
* any component with a two-dimensional array that specifies
|
||||
* grid width/height.
|
||||
*
|
||||
* Example: addRow(grid, "labeltxt", _entryField, _bigThing, [2, 2], _smallThing);
|
||||
*
|
||||
* All will be put in the same row, but bigThing will have
|
||||
* colspan=2 rowspan=2
|
||||
*/
|
||||
public static function addRow (grid :Grid, ... specs) :GridRow
|
||||
{
|
||||
var row :GridRow = new GridRow();
|
||||
var lastItem :GridItem;
|
||||
for each (var o :Object in specs) {
|
||||
if (o is String) {
|
||||
var lbl :Label = new Label();
|
||||
lbl.text = String(o);
|
||||
o = lbl;
|
||||
}
|
||||
if (o is UIComponent) {
|
||||
lastItem = addToRow(row, UIComponent(o));
|
||||
|
||||
} else if (o is Array) {
|
||||
var arr :Array = (o as Array);
|
||||
lastItem.colSpan = int(arr[0]);
|
||||
lastItem.rowSpan = int(arr[1]);
|
||||
|
||||
} else {
|
||||
throw new ArgumentError();
|
||||
}
|
||||
}
|
||||
grid.addChild(row);
|
||||
return row;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a child to the specified grid row, returning the
|
||||
* GridItem created for containment of the child.
|
||||
*/
|
||||
public static function addToRow (row :GridRow, comp :UIComponent) :GridItem
|
||||
{
|
||||
var item :GridItem = new GridItem();
|
||||
item.addChild(comp);
|
||||
row.addChild(item);
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of cells in a grid.
|
||||
*/
|
||||
public static function getCellCount (grid :Grid) :int
|
||||
{
|
||||
var count :int = 0;
|
||||
for (var ii :int = 0; ii < grid.numChildren; ii++) {
|
||||
count += (grid.getChildAt(ii) as GridRow).numChildren;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the contents of the specified cell. Not super fast, as it
|
||||
* checks each row in order.
|
||||
*/
|
||||
public static function getCellAt (grid :Grid, index :int) :UIComponent
|
||||
{
|
||||
for (var ii :int = 0; ii < grid.numChildren; ii++) {
|
||||
var row :GridRow = grid.getChildAt(ii) as GridRow;
|
||||
if (index < row.numChildren) {
|
||||
return (row.getChildAt(index) as GridItem).getChildAt(0) as UIComponent;
|
||||
} else {
|
||||
index -= row.numChildren; // and then on to the next row
|
||||
}
|
||||
}
|
||||
return null; // never found it. throw an out-of-range error?
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.flex {
|
||||
|
||||
import flash.display.Stage;
|
||||
|
||||
import flash.events.KeyboardEvent;
|
||||
import flash.events.MouseEvent;
|
||||
|
||||
import flash.ui.Keyboard;
|
||||
|
||||
import flash.utils.Dictionary;
|
||||
|
||||
import mx.core.Application;
|
||||
import mx.controls.Button;
|
||||
|
||||
|
||||
public class KeyboardManager
|
||||
{
|
||||
public static function setShortcut (trigger :*, keyCode :uint, ... modifiers) :void
|
||||
{
|
||||
_triggers[trigger] = [ keyCode, modifiers ];
|
||||
ensureListening();
|
||||
}
|
||||
|
||||
public static function clearShortcut (trigger :*) :void
|
||||
{
|
||||
delete _triggers[trigger];
|
||||
}
|
||||
|
||||
protected static function ensureListening () :void
|
||||
{
|
||||
var stage :Stage = Application(Application.application).stage;
|
||||
stage.addEventListener(KeyboardEvent.KEY_DOWN, handleKeyDown);
|
||||
}
|
||||
|
||||
protected static function handleKeyDown (event :KeyboardEvent) :void
|
||||
{
|
||||
for (var trigger :* in _triggers) {
|
||||
var info :Array = _triggers[trigger] as Array;
|
||||
var keyCode :uint = info[0];
|
||||
var modifiers :Array = info[1] as Array;
|
||||
if (event.keyCode == keyCode &&
|
||||
// (event.altKey == (-1 != modifierCodes.indexOf(Keyboard.ALTERNATE))) &&
|
||||
(event.ctrlKey == (-1 != modifiers.indexOf(Keyboard.CONTROL))) &&
|
||||
(event.shiftKey == (-1 != modifiers.indexOf(Keyboard.SHIFT)))) {
|
||||
wasTriggered(trigger);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static function wasTriggered (trigger :*) :void
|
||||
{
|
||||
if (trigger is Button) {
|
||||
var button :Button = Button(trigger);
|
||||
if (button.stage != null && button.enabled) {
|
||||
button.dispatchEvent(new MouseEvent(MouseEvent.CLICK));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static const _triggers :Dictionary = new Dictionary(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.flex {
|
||||
|
||||
import mx.core.ScrollPolicy;
|
||||
|
||||
import mx.containers.HBox;
|
||||
|
||||
import mx.controls.Label;
|
||||
|
||||
import mx.controls.sliderClasses.Slider;
|
||||
|
||||
import mx.events.SliderEvent;
|
||||
|
||||
/**
|
||||
* A simple component that displays a label to the left of a slider.
|
||||
*/
|
||||
public class LabeledSlider extends HBox
|
||||
{
|
||||
/** The actual slider. */
|
||||
public var slider :Slider;
|
||||
|
||||
/**
|
||||
* Create a LabeledSlider holding the specified slider.
|
||||
*/
|
||||
public function LabeledSlider (slider :Slider, labelWidth :int = 17)
|
||||
{
|
||||
_label = new Label();
|
||||
_label.text = String(slider.value);
|
||||
_label.width = labelWidth;
|
||||
|
||||
horizontalScrollPolicy = ScrollPolicy.OFF;
|
||||
setStyle("horizontalGap", 2);
|
||||
|
||||
addChild(_label);
|
||||
this.slider = slider;
|
||||
slider.showDataTip = false; // because we do it...
|
||||
addChild(slider);
|
||||
|
||||
slider.addEventListener(SliderEvent.CHANGE, handleSliderChange, false, 0, true);
|
||||
}
|
||||
|
||||
override public function set width (width :Number) :void
|
||||
{
|
||||
super.width = width;
|
||||
slider.width = width - _label.width - 2;
|
||||
}
|
||||
|
||||
protected function handleSliderChange (event :SliderEvent) :void
|
||||
{
|
||||
_label.text = String(event.value);
|
||||
}
|
||||
|
||||
protected var _label :Label;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.flex {
|
||||
|
||||
import flash.display.DisplayObjectContainer;
|
||||
|
||||
import mx.core.ContainerCreationPolicy;
|
||||
import mx.core.UIComponent;
|
||||
import mx.containers.VBox;
|
||||
|
||||
public class LazyContainer extends VBox
|
||||
{
|
||||
/**
|
||||
* Create a lazy container that will not create its children
|
||||
* until needed.
|
||||
*
|
||||
* @param creation a function that takes no args and returns
|
||||
* a UIComponent to be added to the container.
|
||||
*/
|
||||
public function LazyContainer (creation :Function)
|
||||
{
|
||||
_creation = creation;
|
||||
creationPolicy = ContainerCreationPolicy.NONE;
|
||||
}
|
||||
|
||||
override public function createComponentsFromDescriptors (
|
||||
recurse :Boolean = true) :void
|
||||
{
|
||||
super.createComponentsFromDescriptors(recurse);
|
||||
|
||||
if (_creation != null) {
|
||||
addChild(_creation() as UIComponent);
|
||||
_creation = null; // assist gc
|
||||
}
|
||||
}
|
||||
|
||||
protected var _creation :Function;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.flex {
|
||||
|
||||
import mx.core.ContainerCreationPolicy;
|
||||
|
||||
import mx.core.UIComponent;
|
||||
|
||||
import mx.containers.TabNavigator;
|
||||
import mx.containers.VBox;
|
||||
|
||||
/**
|
||||
* A tab navigator that is specially set up for lazy-creating the
|
||||
* content in each tab.
|
||||
*/
|
||||
public class LazyTabNavigator extends TabNavigator
|
||||
{
|
||||
public function LazyTabNavigator ()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a tab to the container. The creation function takes no args and
|
||||
* returns a UIComponent.
|
||||
*/
|
||||
public function addTab (label :String, creation :Function) :void
|
||||
{
|
||||
addTabAt(label, creation, numChildren);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a tab to the container at the specified index.
|
||||
* The creation function takes no args and returns a UIComponent.
|
||||
*/
|
||||
public function addTabAt (
|
||||
label :String, creation :Function, index :int) :void
|
||||
{
|
||||
var box :LazyContainer = new LazyContainer(creation);
|
||||
box.label = label;
|
||||
addChildAt(box, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.flex {
|
||||
|
||||
import flash.display.Loader;
|
||||
import flash.display.Sprite;
|
||||
|
||||
import mx.core.IFlexAsset;
|
||||
import mx.core.IFlexDisplayObject;
|
||||
import mx.core.IInvalidating;
|
||||
|
||||
import com.threerings.util.MultiLoader;
|
||||
|
||||
/**
|
||||
* An asset that can be used in Flex, that's loaded via a Loader.
|
||||
*/
|
||||
public class LoadedAsset extends Sprite
|
||||
implements IFlexAsset, IFlexDisplayObject
|
||||
{
|
||||
/**
|
||||
* @param source a String (url) or URLRequest, or Class or a ByteArray.
|
||||
*/
|
||||
public function LoadedAsset (source :Object) :void
|
||||
{
|
||||
MultiLoader.getLoaders(source, handleLoaded);
|
||||
}
|
||||
|
||||
override public function get height () :Number
|
||||
{
|
||||
return (_loader == null) ? _measHeight : super.height;
|
||||
}
|
||||
|
||||
override public function set height (value :Number) :void
|
||||
{
|
||||
if (_loader == null) {
|
||||
_reqHeight = value;
|
||||
} else {
|
||||
_loader.height = value;
|
||||
}
|
||||
}
|
||||
|
||||
override public function get width () :Number
|
||||
{
|
||||
return (_loader == null) ? _measWidth : super.width;
|
||||
}
|
||||
|
||||
override public function set width (value :Number) :void
|
||||
{
|
||||
if (_loader == null) {
|
||||
_reqWidth = value;
|
||||
} else {
|
||||
_loader.width = value;
|
||||
}
|
||||
}
|
||||
|
||||
// from IFlexDisplayObject
|
||||
public function get measuredHeight () :Number
|
||||
{
|
||||
return _measHeight;
|
||||
}
|
||||
|
||||
// from IFlexDisplayObject
|
||||
public function get measuredWidth () :Number
|
||||
{
|
||||
return _measWidth;
|
||||
}
|
||||
|
||||
// from IFlexDisplayObject
|
||||
public function move (x :Number, y :Number) :void
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
// from IFlexDisplayObject
|
||||
public function setActualSize (newWidth :Number, newHeight :Number) :void
|
||||
{
|
||||
width = newWidth;
|
||||
height = newHeight;
|
||||
}
|
||||
|
||||
protected function handleLoaded (loader :Loader) :void
|
||||
{
|
||||
_loader = loader;
|
||||
addChild(_loader);
|
||||
|
||||
// Note our measured size
|
||||
_measWidth = loader.width;
|
||||
_measHeight = loader.height;
|
||||
|
||||
// set any size that was set while we were being loaded
|
||||
if (!isNaN(_reqWidth)) {
|
||||
loader.width = _reqWidth;
|
||||
}
|
||||
if (!isNaN(_reqHeight)) {
|
||||
loader.height = _reqHeight;
|
||||
}
|
||||
|
||||
// if we have a parent that knows about sizes, tell it we now know our size
|
||||
if (parent is IInvalidating) {
|
||||
IInvalidating(parent).invalidateSize();
|
||||
}
|
||||
}
|
||||
|
||||
protected var _loader :Loader;
|
||||
|
||||
// initial / base sizes
|
||||
protected var _measWidth :Number = 0;
|
||||
protected var _measHeight :Number = 0;
|
||||
|
||||
// requested sizes, during loading
|
||||
protected var _reqWidth :Number;
|
||||
protected var _reqHeight :Number;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.flex {
|
||||
|
||||
import flash.geom.Point;
|
||||
import flash.geom.Rectangle;
|
||||
|
||||
import mx.core.UIComponent;
|
||||
|
||||
import com.threerings.display.DisplayUtil;
|
||||
|
||||
/**
|
||||
* Flex popup utilities.
|
||||
*/
|
||||
public class PopUpUtil
|
||||
{
|
||||
/**
|
||||
* Center the popup inside the stage, to which it should already be added.
|
||||
*/
|
||||
public static function center (popup :UIComponent) :void
|
||||
{
|
||||
centerInRect(popup, createStageRect(popup));
|
||||
}
|
||||
|
||||
/**
|
||||
* Center the popup within the specified rectangle.
|
||||
*/
|
||||
public static function centerInRect (popup :UIComponent, rect :Rectangle) :void
|
||||
{
|
||||
var p :Point = DisplayUtil.centerRectInRect(new Rectangle(0, 0,
|
||||
popup.getExplicitOrMeasuredWidth(), popup.getExplicitOrMeasuredHeight()), rect);
|
||||
popup.x = p.x;
|
||||
popup.y = p.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fit the popup inside the stage.
|
||||
*/
|
||||
public static function fit (popup :UIComponent) :void
|
||||
{
|
||||
fitInRect(popup, createStageRect(popup));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fit the popup inside the specified rectangle.
|
||||
*/
|
||||
public static function fitInRect (popup :UIComponent, rect :Rectangle) :void
|
||||
{
|
||||
var p :Point = DisplayUtil.fitRectInRect(createPopupRect(popup), rect);
|
||||
popup.x = p.x;
|
||||
popup.y = p.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to fit inside the specified rectangle, also avoiding other popups.
|
||||
*
|
||||
* @param popup the popup we'll try to move.
|
||||
* @param bounds the boundary within which to place the popup.
|
||||
* @param padding extra padding to place between popups (but not around the inside of
|
||||
* the bounds, do that yourself if you want it).
|
||||
*/
|
||||
public static function avoidOtherPopups (
|
||||
popup :UIComponent, bounds :Rectangle, padding :int = 0) :void
|
||||
{
|
||||
var avoid :Array = [];
|
||||
var r :Rectangle;
|
||||
|
||||
// find the other popups we need to avoid
|
||||
for (var ii :int = popup.parent.numChildren - 1; ii >= 0; ii--) {
|
||||
var comp :UIComponent = popup.parent.getChildAt(ii) as UIComponent;
|
||||
if (comp != null && comp.isPopUp && comp.visible && comp != popup) {
|
||||
r = createPopupRect(comp);
|
||||
r.inflate(padding, padding);
|
||||
avoid.push(r);
|
||||
}
|
||||
}
|
||||
|
||||
r = createPopupRect(popup);
|
||||
// put the rectangle in-bounds, so that even if avoiding the others fails, we're
|
||||
// at least in-bounds when fitRectInRect resets to the original position.
|
||||
var p :Point = DisplayUtil.fitRectInRect(r, bounds);
|
||||
r.x = p.x;
|
||||
r.y = p.y; // assigning to topLeft adjusts width/height too, so don't do it!
|
||||
DisplayUtil.positionRect(r, bounds, avoid);
|
||||
popup.x = r.x;
|
||||
popup.y = r.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wee utility method.
|
||||
*/
|
||||
protected static function createStageRect (popup :UIComponent) :Rectangle
|
||||
{
|
||||
return new Rectangle(0, 0, popup.stage.stageWidth, popup.stage.stageHeight);
|
||||
}
|
||||
|
||||
protected static function createPopupRect (popup :UIComponent) :Rectangle
|
||||
{
|
||||
return new Rectangle(popup.x, popup.y,
|
||||
popup.getExplicitOrMeasuredWidth(), popup.getExplicitOrMeasuredHeight());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.flex {
|
||||
|
||||
import flash.display.DisplayObject;
|
||||
import flash.display.Graphics;
|
||||
import flash.display.Shape;
|
||||
import flash.display.Sprite;
|
||||
|
||||
import flash.events.Event;
|
||||
import flash.events.MouseEvent;
|
||||
|
||||
import flash.geom.Matrix;
|
||||
import flash.geom.Rectangle;
|
||||
|
||||
import mx.containers.Canvas;
|
||||
|
||||
/**
|
||||
* A control for scrolling some component separately.
|
||||
*/
|
||||
public class ScrollBox extends Canvas
|
||||
{
|
||||
public function ScrollBox (
|
||||
target :DisplayObject, maxWidth :int, maxHeight :int)
|
||||
{
|
||||
_target = target;
|
||||
_maxWidth = maxWidth;
|
||||
_maxHeight = maxHeight;
|
||||
|
||||
opaqueBackground = 0xFFFFFF;
|
||||
|
||||
_mask = new Shape();
|
||||
rawChildren.addChild(_mask);
|
||||
this.mask = _mask;
|
||||
|
||||
_box = new Sprite();
|
||||
rawChildren.addChild(_box);
|
||||
|
||||
recheckBounds();
|
||||
|
||||
// TODO: only when something changes
|
||||
addEventListener(Event.ENTER_FRAME, enterFrame);
|
||||
|
||||
_box.addEventListener(MouseEvent.MOUSE_DOWN, spritePressed);
|
||||
}
|
||||
|
||||
override public function setActualSize (w :Number, h :Number) :void
|
||||
{
|
||||
super.setActualSize(w, h);
|
||||
|
||||
recheckBounds();
|
||||
|
||||
graphics.clear();
|
||||
graphics.lineStyle(1, 0x000000, 1.0);
|
||||
graphics.drawRect(0, 0, width, height);
|
||||
|
||||
var g :Graphics = _mask.graphics;
|
||||
g.clear();
|
||||
g.beginFill(0xffffff, 0.0);
|
||||
g.drawRect(0, 0, width, height);
|
||||
g.endFill();
|
||||
}
|
||||
|
||||
protected function enterFrame (vent :Event) :void
|
||||
{
|
||||
if (!_dragging) {
|
||||
recheckBounds();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bounds of the scrollable area. Broken-out for easy overridding.
|
||||
*/
|
||||
protected function getScrollBounds () :Rectangle
|
||||
{
|
||||
var bounds :Rectangle = _target.transform.pixelBounds;
|
||||
// var m :Matrix = _target.transform.concatenatedMatrix;
|
||||
// bounds.topLeft = m.transformPoint(bounds.topLeft);
|
||||
// bounds.bottomRight = m.transformPoint(bounds.bottomRight);
|
||||
return bounds;
|
||||
}
|
||||
|
||||
protected function recheckBounds () :void
|
||||
{
|
||||
var bounds :Rectangle = getScrollBounds();
|
||||
|
||||
// see what's visible, what's not..
|
||||
var scroller :Rectangle = _target.scrollRect;
|
||||
if (scroller == null) {
|
||||
scroller = bounds.clone();
|
||||
}
|
||||
|
||||
if ((_bounds != null) && _bounds.equals(bounds) &&
|
||||
(_scroller != null) && _scroller.equals(scroller)) {
|
||||
return;
|
||||
}
|
||||
|
||||
_bounds = bounds.clone();
|
||||
_scroller = scroller;
|
||||
|
||||
_scale = Math.min(1, Math.min(_maxWidth / _bounds.width, _maxHeight / _bounds.height));
|
||||
|
||||
width = _scale * _bounds.width;
|
||||
height = _scale * _bounds.height;
|
||||
|
||||
drawBox(_box, _scroller.width * _scale, _scroller.height * _scale);
|
||||
|
||||
_box.x = (_scroller.x - _bounds.x) * _scale;
|
||||
_box.y = (_scroller.y - _bounds.y) * _scale;
|
||||
}
|
||||
|
||||
protected function drawBox (box :Sprite, ww :Number, hh :Number) :void
|
||||
{
|
||||
var g :Graphics = _box.graphics;
|
||||
g.clear();
|
||||
g.beginFill(0xffffff, 0.3);
|
||||
g.lineStyle(1, 0x000000, 0.3);
|
||||
g.drawRect(0, 0, ww, hh);
|
||||
g.endFill();
|
||||
}
|
||||
|
||||
protected function spritePressed (evt :MouseEvent) :void
|
||||
{
|
||||
_box.addEventListener(Event.ENTER_FRAME, boxUpdate);
|
||||
_box.startDrag(true,
|
||||
new Rectangle(0, 0, width - _box.width, height - _box.height));
|
||||
stage.addEventListener(MouseEvent.MOUSE_UP, spriteReleased);
|
||||
_dragging = true;
|
||||
}
|
||||
|
||||
protected function spriteReleased (evt :MouseEvent) :void
|
||||
{
|
||||
stage.removeEventListener(MouseEvent.MOUSE_UP, spriteReleased);
|
||||
_box.stopDrag();
|
||||
_box.removeEventListener(Event.ENTER_FRAME, boxUpdate);
|
||||
_dragging = false;
|
||||
}
|
||||
|
||||
protected function boxUpdate (evt :Event) :void
|
||||
{
|
||||
var r :Rectangle = _target.scrollRect;
|
||||
r.x = (_box.x / _scale) + _bounds.x;
|
||||
r.y = (_box.y / _scale) + _bounds.y;
|
||||
_target.scrollRect = r;
|
||||
}
|
||||
|
||||
protected var _dragging :Boolean;
|
||||
|
||||
protected var _target :DisplayObject;
|
||||
|
||||
protected var _maxWidth :int;
|
||||
protected var _maxHeight :int;
|
||||
|
||||
protected var _box :Sprite;
|
||||
protected var _mask :Shape;
|
||||
|
||||
protected var _scale :Number;
|
||||
|
||||
protected var _bounds :Rectangle;
|
||||
protected var _scroller :Rectangle;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.flex.menuClasses {
|
||||
|
||||
import mx.core.IFlexDisplayObject;
|
||||
import mx.core.IUIComponent;
|
||||
|
||||
import mx.controls.menuClasses.MenuListData;
|
||||
|
||||
public class CommandListData extends MenuListData
|
||||
{
|
||||
/** An already-instantiated icon, to use if the icon property is null. */
|
||||
public var iconObject :IFlexDisplayObject;
|
||||
|
||||
public function CommandListData (text :String, icon :Class, iconObject :IFlexDisplayObject,
|
||||
labelField :String, uid :String, owner :IUIComponent,
|
||||
rowIndex :int = 0, columnIndex :int = 0)
|
||||
{
|
||||
super(text, icon, labelField, uid, owner, rowIndex, columnIndex);
|
||||
this.iconObject = iconObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.flex.menuClasses {
|
||||
|
||||
import flash.display.DisplayObject;
|
||||
|
||||
import mx.controls.Menu;
|
||||
import mx.controls.menuClasses.IMenuDataDescriptor;
|
||||
import mx.controls.menuClasses.MenuItemRenderer;
|
||||
|
||||
public class CommandMenuItemRenderer extends MenuItemRenderer
|
||||
{
|
||||
public function CommandMenuItemRenderer ()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
override protected function commitProperties () :void
|
||||
{
|
||||
super.commitProperties();
|
||||
// Note: a menu with no items will have a null listData
|
||||
|
||||
if ((icon == null) && (listData is CommandListData)) {
|
||||
icon = CommandListData(listData).iconObject;
|
||||
if (icon != null) {
|
||||
addChild(DisplayObject(icon));
|
||||
}
|
||||
}
|
||||
|
||||
if (label.visible && (listData != null)) {
|
||||
var dataDescriptor :IMenuDataDescriptor = Menu(listData.owner).dataDescriptor;
|
||||
var typeVal :String = dataDescriptor.getType(data);
|
||||
if (typeVal == "title") {
|
||||
dataDescriptor.setEnabled(data, false);
|
||||
label.enabled = false;
|
||||
label.styleName = "menuTitle";
|
||||
invalidateDisplayList();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.media {
|
||||
|
||||
public interface Tickable
|
||||
{
|
||||
function tick (tickStamp :int) :void;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.media {
|
||||
|
||||
import flash.events.TimerEvent;
|
||||
import flash.utils.Timer;
|
||||
|
||||
import com.threerings.util.Arrays;
|
||||
|
||||
/**
|
||||
* Registers objects that wish to be sent a tick() call once per frame.
|
||||
*/
|
||||
public class Ticker
|
||||
{
|
||||
public function start () :void
|
||||
{
|
||||
_t.addEventListener(TimerEvent.TIMER, handleTimer);
|
||||
_start = new Date().time;
|
||||
_t.start();
|
||||
}
|
||||
|
||||
public function stop () :void
|
||||
{
|
||||
_t.removeEventListener(TimerEvent.TIMER, handleTimer);
|
||||
_t.stop();
|
||||
|
||||
}
|
||||
|
||||
public function handleTimer (event :TimerEvent) :void
|
||||
{
|
||||
tick(int(new Date().time - _start));
|
||||
}
|
||||
|
||||
protected function tick (tickStamp :int) :void
|
||||
{
|
||||
for each (var tickable :Tickable in _tickables) {
|
||||
tickable.tick(tickStamp);
|
||||
}
|
||||
}
|
||||
|
||||
public function registerTickable (tickable :Tickable) :void
|
||||
{
|
||||
_tickables.push(tickable);
|
||||
}
|
||||
|
||||
public function removeTickable (tickable :Tickable) :void
|
||||
{
|
||||
Arrays.removeFirst(_tickables, tickable);
|
||||
}
|
||||
|
||||
/** Everyone who wants to hear about our ticks. */
|
||||
protected var _tickables :Array = [];
|
||||
|
||||
/** A timer that will fire every "frame". */
|
||||
protected var _t :Timer = new Timer(1);
|
||||
|
||||
/** What time our ticker started running - tickStamps will be relative to this time. */
|
||||
protected var _start :Number;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.media.image {
|
||||
|
||||
import com.threerings.util.Log;
|
||||
import com.threerings.util.Map;
|
||||
import com.threerings.util.Maps;
|
||||
import com.threerings.util.RandomUtil;
|
||||
import com.threerings.util.StringUtil;
|
||||
|
||||
public class ClassRecord
|
||||
{
|
||||
private const log :Log = Log.getLog(ClassRecord);
|
||||
|
||||
/** An integer identifier for this class. */
|
||||
public var classId :int;
|
||||
|
||||
/** The name of the color class. */
|
||||
public var name :String;
|
||||
|
||||
/** The source color to use when recoloring colors in this class. */
|
||||
public var source :uint;
|
||||
|
||||
/** Data identifying the range of colors around the source color
|
||||
* that will be recolored when recoloring using this class. */
|
||||
public var range :Array;
|
||||
|
||||
/** The default starting legality value for this color class. See
|
||||
* {@link ColorRecord#starter}. */
|
||||
public var starter :Boolean;
|
||||
|
||||
/** The default colorId to use for recoloration in this class, or
|
||||
* 0 if there is no default defined. */
|
||||
public var defaultId :int;
|
||||
|
||||
/** A table of target colors included in this class. */
|
||||
public var colors :Map = Maps.newMapOf(int);
|
||||
|
||||
/** Used when parsing the color definitions. */
|
||||
public function addColor (record :ColorRecord) :void
|
||||
{
|
||||
// validate the color id
|
||||
if (record.colorId > 127) {
|
||||
log.warning("Refusing to add color record; colorId > 127",
|
||||
"class", this, "record", record);
|
||||
} else if (colors.containsKey(record.colorId)) {
|
||||
log.warning("Refusing to add duplicate colorId",
|
||||
"class", this, "record", record, "existing", colors.get(record.colorId));
|
||||
} else {
|
||||
record.cclass = this;
|
||||
colors.put(record.colorId, record);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates a color identified in string form into the id that should be used to look up
|
||||
* its information. Throws an exception if no color could be found that associates with
|
||||
* that name.
|
||||
*/
|
||||
public function getColorId (name :String) :int
|
||||
{
|
||||
// Check if the string is itself a number
|
||||
var id :int = name as int;
|
||||
if (colors.containsKey(id)) {
|
||||
return id;
|
||||
}
|
||||
|
||||
// Look for name matches among all colors
|
||||
for each (var color :ColorRecord in colors.values()) {
|
||||
if (StringUtil.compareIgnoreCase(color.name, name) == 0) {
|
||||
return color.colorId;
|
||||
}
|
||||
}
|
||||
|
||||
// That input wasn't a color
|
||||
throw new Error("No color named '" + name + "'", 0);
|
||||
}
|
||||
|
||||
/** Returns a random starting id from the entries in this class. */
|
||||
public function randomStartingColor () :ColorRecord
|
||||
{
|
||||
// figure out our starter ids if we haven't already
|
||||
if (_starters == null) {
|
||||
_starters = [];
|
||||
for each (var color :ColorRecord in colors) {
|
||||
if (color.starter) {
|
||||
_starters.push(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sanity check
|
||||
if (_starters.length < 1) {
|
||||
log.warning("Requested random starting color from colorless component class",
|
||||
"class", this);
|
||||
return null;
|
||||
}
|
||||
|
||||
// return a random entry from the array
|
||||
return RandomUtil.pickRandom(_starters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default ColorRecord defined for this color class, or
|
||||
* null if none.
|
||||
*/
|
||||
public function getDefault () :ColorRecord
|
||||
{
|
||||
return colors.get(defaultId);
|
||||
}
|
||||
|
||||
public function toString () :String
|
||||
{
|
||||
return "[id=" + classId + ", name=" + name + ", source=#" +
|
||||
StringUtil.toHex(source & 0xFFFFFF, 6) +
|
||||
", range=" + StringUtil.toString(range) +
|
||||
", starter=" + starter + ", colors=" +
|
||||
StringUtil.toString(colors.values()) + "]";
|
||||
}
|
||||
|
||||
public static function fromXml (xml :XML) :ClassRecord
|
||||
{
|
||||
var rec :ClassRecord = new ClassRecord();
|
||||
rec.classId = xml.@classId;
|
||||
for each (var colorXml :XML in xml.color) {
|
||||
rec.colors.put(int(colorXml.@colorId), ColorRecord.fromXml(colorXml, rec));
|
||||
}
|
||||
|
||||
rec.name = xml.@name;
|
||||
var srcStr :String = xml.@source;
|
||||
rec.source = parseInt(srcStr.substr(1, srcStr.length - 1), 16);
|
||||
rec.range = toNumArray(xml.@range);
|
||||
rec.defaultId = xml.@defaultId;
|
||||
|
||||
return rec;
|
||||
}
|
||||
|
||||
protected static function toNumArray (str :String) :Array
|
||||
{
|
||||
if (str == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return str.split(",").map(function(element :*, index :int, arr :Array) :Number {
|
||||
return Number(element);
|
||||
});
|
||||
}
|
||||
|
||||
protected var _starters :Array;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.media.image {
|
||||
|
||||
import com.threerings.util.Log;
|
||||
import com.threerings.util.Map;
|
||||
import com.threerings.util.Maps;
|
||||
import com.threerings.util.StringUtil;
|
||||
|
||||
public class ColorPository
|
||||
{
|
||||
private const log :Log = Log.getLog(ColorPository);
|
||||
|
||||
public function getClasses () :Array
|
||||
{
|
||||
return _classes.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array containing the records for the colors in the
|
||||
* specified class.
|
||||
*/
|
||||
public function getColors (className :String) :Array
|
||||
{
|
||||
// make sure the class exists
|
||||
var record :ClassRecord = getClassRecordByName(className);
|
||||
if (record == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// create the array
|
||||
return record.colors.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array containing the ids of the colors in the specified
|
||||
* class.
|
||||
*/
|
||||
public function getColorIds (className :String) :Array
|
||||
{
|
||||
// make sure the class exists
|
||||
var record :ClassRecord = getClassRecordByName(className);
|
||||
if (record == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return record.colors.values().map(
|
||||
function(element :ColorRecord, index :int, arr :Array) :int {
|
||||
return element.colorId;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified color is legal for use at character creation time. false is
|
||||
* always returned for non-existent colors or classes.
|
||||
*/
|
||||
public function isLegalStartColor (classId :int, colorId :int) :Boolean
|
||||
{
|
||||
var color :ColorRecord = getColorRecord(classId, colorId);
|
||||
return (color == null) ? false : color.starter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a random starting color from the specified color class.
|
||||
*/
|
||||
public function getRandomStartingColor (className :String) :ColorRecord
|
||||
{
|
||||
// make sure the class exists
|
||||
var record :ClassRecord = getClassRecordByName(className);
|
||||
return (record == null) ? null : record.randomStartingColor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up a colorization by id.
|
||||
*/
|
||||
public function getColorization (classId :int, colorId :int) :Colorization
|
||||
{
|
||||
var color :ColorRecord = getColorRecord(classId, colorId);
|
||||
return (color == null) ? null : color.getColorization();
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up a colorization by color print.
|
||||
*/
|
||||
public function getColorizationByPrint (colorPrint :int) :Colorization
|
||||
{
|
||||
return getColorization(colorPrint >> 8, colorPrint & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up a colorization by class and color names.
|
||||
*/
|
||||
public function getColorizationByName (className :String, colorName :String) :Colorization
|
||||
{
|
||||
var crec :ClassRecord = getClassRecordByName(className);
|
||||
if (crec != null) {
|
||||
var colorId :int = crec.getColorId(colorName);
|
||||
|
||||
var color :ColorRecord = crec.colors.get(colorId);
|
||||
if (color != null) {
|
||||
return color.getColorization();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up a colorization by class and color Id.
|
||||
*/
|
||||
public function getColorizationByNameAndId (className :String, colorId :int) :Colorization
|
||||
{
|
||||
var crec :ClassRecord = getClassRecordByName(className);
|
||||
if (crec != null) {
|
||||
var color :ColorRecord = crec.colors.get(colorId);
|
||||
if (color != null) {
|
||||
return color.getColorization();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads up a colorization class by name and logs a warning if it doesn't exist.
|
||||
*/
|
||||
public function getClassRecordByName (className :String) :ClassRecord
|
||||
{
|
||||
for each (var crec :ClassRecord in _classes.values()) {
|
||||
if (crec.name == className) {
|
||||
return crec;
|
||||
}
|
||||
}
|
||||
log.warning("No such color class", "class", className, new Error());
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the requested color class record.
|
||||
*/
|
||||
public function getClassRecord (classId :int) :ClassRecord
|
||||
{
|
||||
return _classes.get(classId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the requested color record.
|
||||
*/
|
||||
public function getColorRecord (classId :int, colorId :int) :ColorRecord
|
||||
{
|
||||
var record :ClassRecord = getClassRecord(classId);
|
||||
if (record == null) {
|
||||
// if they request color class zero, we assume they're just
|
||||
// decoding a blank colorprint, otherwise we complain
|
||||
if (classId != 0) {
|
||||
log.warning("Requested unknown color class",
|
||||
"classId", classId, "colorId", colorId, new Error());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return record.colors.get(colorId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the requested color record by class & color names.
|
||||
*/
|
||||
public function getColorRecordByName (className :String, colorName :String) :ColorRecord
|
||||
{
|
||||
var record :ClassRecord = getClassRecordByName(className);
|
||||
if (record == null) {
|
||||
log.warning("Requested unknown color class",
|
||||
"className", className, "colorName", colorName, new Error());
|
||||
return null;
|
||||
}
|
||||
|
||||
var colorId :int = record.getColorId(colorName);
|
||||
|
||||
return record.colors.get(colorId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads up a serialized color pository from the supplied resource
|
||||
* manager.
|
||||
*/
|
||||
public static function fromXml (xml :XML) :ColorPository
|
||||
{
|
||||
var pos :ColorPository = new ColorPository();
|
||||
for each (var classXml :XML in xml.elements("class")) {
|
||||
pos._classes.put(int(classXml.@classId), ClassRecord.fromXml(classXml));
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
/** Our mapping from class names to class records. */
|
||||
protected var _classes :Map = Maps.newMapOf(int);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.media.image {
|
||||
|
||||
import com.threerings.util.Log;
|
||||
import com.threerings.util.StringUtil;
|
||||
|
||||
public class ColorRecord
|
||||
{
|
||||
private const log :Log = Log.getLog(ColorRecord);
|
||||
|
||||
/** The colorization class to which we belong. */
|
||||
public var cclass :ClassRecord;
|
||||
|
||||
/** A unique colorization identifier (used in fingerprints). */
|
||||
public var colorId :int;
|
||||
|
||||
/** The name of the target color. */
|
||||
public var name :String;
|
||||
|
||||
/** Data indicating the offset (in HSV color space) from the
|
||||
* source color to recolor to this color. */
|
||||
public var offsets :Array;
|
||||
|
||||
/** Tags this color as a legal starting color or not. This is a
|
||||
* shameful copout, placing application-specific functionality
|
||||
* into a general purpose library class. */
|
||||
public var starter :Boolean;
|
||||
|
||||
/**
|
||||
* Returns a value that is the composite of our class id and color
|
||||
* id which can be used to identify a colorization record. This
|
||||
* value will always be a positive integer that fits into 16 bits.
|
||||
*/
|
||||
public function getColorPrint () :int
|
||||
{
|
||||
return ((cclass.classId << 8) | colorId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data in this record configured as a colorization
|
||||
* instance.
|
||||
*/
|
||||
public function getColorization () :Colorization
|
||||
{
|
||||
return new Colorization(getColorPrint(), cclass.source,
|
||||
cclass.range, offsets);
|
||||
}
|
||||
|
||||
public function toString () :String
|
||||
{
|
||||
return "[id=" + colorId + ", name=" + name +
|
||||
", offsets=" + StringUtil.toString(offsets) +
|
||||
", starter=" + starter + "]";
|
||||
}
|
||||
|
||||
|
||||
public static function fromXml (xml :XML, cclass :ClassRecord) :ColorRecord
|
||||
{
|
||||
var rec :ColorRecord = new ColorRecord();
|
||||
rec.colorId = xml.@colorId;
|
||||
rec.name = xml.@name;
|
||||
rec.offsets = toNumArray(xml.@offsets);
|
||||
rec.starter = xml.@starter;
|
||||
rec.cclass = cclass;
|
||||
|
||||
return rec;
|
||||
}
|
||||
|
||||
protected static function toNumArray (str :String) :Array
|
||||
{
|
||||
if (str == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return str.split(",").map(function(element :String, index :int, arr :Array) :Number {
|
||||
return Number(StringUtil.trim(element));
|
||||
});
|
||||
}
|
||||
|
||||
/** Our data represented as a colorization. */
|
||||
protected var _zation :Colorization;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.media.image {
|
||||
|
||||
import com.threerings.display.ColorUtil;
|
||||
import com.threerings.util.Hashable;
|
||||
import com.threerings.util.Short;
|
||||
import com.threerings.util.StringUtil;
|
||||
|
||||
public class Colorization
|
||||
implements Hashable
|
||||
{
|
||||
/** Every colorization must have a unique id that can be used to
|
||||
* compare a particular colorization record with another. */
|
||||
public var colorizationId :int;
|
||||
|
||||
/** The root color for the colorization. */
|
||||
public var rootColor :uint;
|
||||
|
||||
/** The range around the root color that will be colorized (in
|
||||
* delta-hue, delta-saturation, delta-value. Note that this range is inclusive. */
|
||||
public var range :Array;
|
||||
|
||||
/** The adjustments to make to hue, saturation and value. */
|
||||
public var offsets :Array;
|
||||
|
||||
/**
|
||||
* Constructs a colorization record with the specified identifier.
|
||||
*/
|
||||
public function Colorization (colorizationId :int, rootColor :uint,
|
||||
range :Array, offsets :Array)
|
||||
{
|
||||
this.colorizationId = colorizationId;
|
||||
this.rootColor = rootColor;
|
||||
this.range = range;
|
||||
this.offsets = offsets;
|
||||
|
||||
// compute our HSV and fixed HSV
|
||||
_hsv = ColorUtil.RGBtoHSB(ColorUtil.getRed(rootColor), ColorUtil.getGreen(rootColor),
|
||||
ColorUtil.getBlue(rootColor));
|
||||
_fhsv = toFixedHSV(_hsv);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the root color adjusted by the colorization.
|
||||
*/
|
||||
public function getColorizedRoot () :uint
|
||||
{
|
||||
return recolorColor(_hsv);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the supplied color by the offests in this colorization,
|
||||
* taking the appropriate measures for hue (wrapping it around) and
|
||||
* saturation and value (clipping).
|
||||
*
|
||||
* @return the RGB value of the recolored color.
|
||||
*/
|
||||
public function recolorColor (hsv :Array) :uint
|
||||
{
|
||||
// for hue, we wrap around
|
||||
var hue :Number = hsv[0] + offsets[0];
|
||||
if (hue > 1.0) {
|
||||
hue -= 1.0;
|
||||
}
|
||||
|
||||
// otherwise we clip
|
||||
var sat :Number = Math.min(Math.max(hsv[1] + offsets[1], 0), 1);
|
||||
var val :Number = Math.min(Math.max(hsv[2] + offsets[2], 0), 1);
|
||||
|
||||
// convert back to RGB space
|
||||
return ColorUtil.HSBtoRGB(hue, sat, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this colorization matches the supplied color, false
|
||||
* otherwise.
|
||||
*
|
||||
* @param hsv the HSV values for the color in question.
|
||||
* @param fhsv the HSV values converted to fixed point via {@link
|
||||
* #toFixedHSV} for the color in question.
|
||||
*/
|
||||
public function matches (hsv :Array, fhsv :Array) :Boolean
|
||||
{
|
||||
// check to see that this color is sufficiently "close" to the
|
||||
// root color based on the supplied distance parameters
|
||||
if (distance(fhsv[0], _fhsv[0], Short.MAX_VALUE) >
|
||||
range[0] * Short.MAX_VALUE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// saturation and value don't wrap around like hue
|
||||
if (Math.abs(_hsv[1] - hsv[1]) > range[1] ||
|
||||
Math.abs(_hsv[2] - hsv[2]) > range[2]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function hashCode () :int
|
||||
{
|
||||
return colorizationId ^ rootColor;
|
||||
}
|
||||
|
||||
public function equals (other :Object) :Boolean
|
||||
{
|
||||
if (other is Colorization) {
|
||||
return (Colorization(other)).colorizationId == colorizationId;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function toString () :String
|
||||
{
|
||||
return String(colorizationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a long string representation of this colorization.
|
||||
*/
|
||||
public function toVerboseString () :String
|
||||
{
|
||||
return StringUtil.toString(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts floating point HSV values to a fixed point integer
|
||||
* representation.
|
||||
*
|
||||
* @param hsv the HSV values to be converted.
|
||||
* @param fhsv the destination array into which the fixed values will
|
||||
* be stored. If this is null, a new array will be created of the
|
||||
* appropriate length.
|
||||
*
|
||||
* @return the <code>fhsv</code> parameter if it was non-null or the
|
||||
* newly created target array.
|
||||
*/
|
||||
public static function toFixedHSV (hsv :Array) :Array
|
||||
{
|
||||
var fhsv :Array = new Array(hsv.length);
|
||||
|
||||
for (var ii :int = 0; ii < hsv.length; ii++) {
|
||||
fhsv[ii] = int(hsv[ii] * Short.MAX_VALUE);
|
||||
}
|
||||
return fhsv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the distance between the supplied to numbers modulo N.
|
||||
*/
|
||||
public static function distance (a :int, b :int, N :int) :int
|
||||
{
|
||||
return (a > b) ? Math.min(a - b, b + N - a) : Math.min(b - a, a + N - b);
|
||||
}
|
||||
|
||||
/** Fixed HSV values for our root color; used when calculating
|
||||
* recolorizations using this colorization. */
|
||||
protected var _fhsv :Array;
|
||||
|
||||
/** HSV values for our root color; used when calculating
|
||||
* recolorizations using this colorization. */
|
||||
protected var _hsv :Array;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.media.image {
|
||||
|
||||
import flash.utils.ByteArray;
|
||||
|
||||
import com.threerings.display.ColorUtil;
|
||||
import com.threerings.util.Log;
|
||||
import com.threerings.util.StringUtil;
|
||||
|
||||
import com.threerings.util.F;
|
||||
public class PngRecolorUtil
|
||||
{
|
||||
private static const log :Log = Log.getLog(PngRecolorUtil);
|
||||
|
||||
protected static function isPng (bytes :ByteArray) :Boolean
|
||||
{
|
||||
if (bytes.length < PNG_HEADER.length) {
|
||||
return false;
|
||||
}
|
||||
for (var ii :int = 0; ii < PNG_HEADER.length; ii++) {
|
||||
if (PNG_HEADER[ii] != bytes[ii]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find the required chunks in the bytes that represent a PNG image. All of the chunks that
|
||||
* are succesfully found are pushed into the returned array in the order in which they were
|
||||
* found in the image.
|
||||
*/
|
||||
protected static function findChunks (pngBytes :ByteArray, chunkTypes :Array) :Array
|
||||
{
|
||||
var chunks :Array = [];
|
||||
if (chunkTypes.length < 1) {
|
||||
return chunks;
|
||||
}
|
||||
|
||||
for (var ii :int = PNG_HEADER.length; ii < pngBytes.length;) {
|
||||
pngBytes.position = ii;
|
||||
var chunk :PngChunk =
|
||||
new PngChunk(ii + 8, pngBytes.readInt(), pngBytes.readUTFBytes(4));
|
||||
|
||||
var idx :int = chunkTypes.indexOf(chunk.type);
|
||||
if (idx >= 0) {
|
||||
chunks.push(chunk);
|
||||
if (chunkTypes.length == 1) {
|
||||
return chunks;
|
||||
} else {
|
||||
chunkTypes.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// 4 bytes each for length, chunk type and CRC
|
||||
ii += 12 + chunk.length;
|
||||
}
|
||||
return chunks;
|
||||
}
|
||||
|
||||
public static function recolorPNG (pngBytes :ByteArray,
|
||||
colors :Array/* of Colorization */) :ByteArray
|
||||
{
|
||||
if (colors == null || colors.length == 0) {
|
||||
return pngBytes;
|
||||
}
|
||||
|
||||
// first, lets make sure we really have a PNG
|
||||
if (!isPng(pngBytes)) {
|
||||
log.warning("recolorPNG received invalid pngBytes", new Error());
|
||||
return pngBytes;
|
||||
}
|
||||
|
||||
var chunks :Array = findChunks(pngBytes, [HEADER_CHUNK, PALETTE_CHUNK]);
|
||||
if (chunks.length != 2 || (chunks[0] as PngChunk).type != HEADER_CHUNK) {
|
||||
log.warning("recolorPNG received an unexected PNG format", "requiredChunksFound",
|
||||
chunks.length, new Error());
|
||||
return pngBytes;
|
||||
}
|
||||
var header :PngChunk = chunks[0] as PngChunk;
|
||||
var palette :PngChunk = chunks[1] as PngChunk;
|
||||
// Check to make sure the header declares this to be an indexed-color PNG
|
||||
var colorType :int = pngBytes[header.idx + COLOR_TYPE_IDX];
|
||||
if (colorType != INDEXED_COLOR_TYPE) {
|
||||
log.warning(
|
||||
"Color Type in PNG header is not indexed-color", "type", colorType, new Error());
|
||||
return pngBytes;
|
||||
}
|
||||
|
||||
initializeCRCTable();
|
||||
// CRC starts with chunk type
|
||||
var crc :uint = 0xFFFFFFFF;
|
||||
for (var ii :int = 0; ii < 4; ii++) {
|
||||
crc = crcCalc(crc, PALETTE_CHUNK.charCodeAt(ii));
|
||||
}
|
||||
pngBytes.position = palette.idx;
|
||||
for (ii = palette.idx; ii < palette.idx + palette.length; ii += 3) {
|
||||
var red :uint = pngBytes.readUnsignedByte();
|
||||
var green :uint = pngBytes.readUnsignedByte();
|
||||
var blue :uint = pngBytes.readUnsignedByte();
|
||||
|
||||
var hsv :Array = ColorUtil.RGBtoHSB(red, green, blue);
|
||||
var fhsv :Array = Colorization.toFixedHSV(hsv);
|
||||
for each (var color :Colorization in colors) {
|
||||
if (color != null && color.matches(hsv, fhsv)) {
|
||||
var newRgb :uint = color.recolorColor(hsv);
|
||||
pngBytes[ii] = red = ColorUtil.getRed(newRgb);
|
||||
pngBytes[ii + 1] = green = ColorUtil.getGreen(newRgb);
|
||||
pngBytes[ii + 2] = blue = ColorUtil.getBlue(newRgb);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
crc = F.foldl([red, green, blue], crc, crcCalc);
|
||||
}
|
||||
crc = uint(crc ^ uint(0xFFFFFFFF));
|
||||
// write out the CRC for the modified color table
|
||||
pngBytes.position = palette.idx + palette.length;
|
||||
pngBytes.writeUnsignedInt(crc);
|
||||
|
||||
return pngBytes;
|
||||
}
|
||||
|
||||
protected static function initializeCRCTable () :void
|
||||
{
|
||||
if (_crcTable != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
_crcTable = [];
|
||||
for (var n :uint = 0; n < 256; n++)
|
||||
{
|
||||
var c :uint = n;
|
||||
for (var k :uint = 0; k < 8; k++)
|
||||
{
|
||||
if (c & 1) {
|
||||
c = uint(uint(0xedb88320) ^ uint(c >>> 1));
|
||||
} else {
|
||||
c = uint(c >>> 1);
|
||||
}
|
||||
}
|
||||
_crcTable[n] = c;
|
||||
}
|
||||
}
|
||||
|
||||
protected static function crcCalc (crc :uint, byte :uint) :uint
|
||||
{
|
||||
return uint(_crcTable[(crc ^ byte) & uint(0xFF)] ^ uint(crc >>> 8));
|
||||
}
|
||||
|
||||
/** The amount to pad error messages by. */
|
||||
private static const ERROR_PADDING :int = 5;
|
||||
|
||||
/** Various Png-recolor related constants. */
|
||||
protected static const PNG_HEADER :Array =
|
||||
[ 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A ];
|
||||
protected static const PALETTE_CHUNK :String = "PLTE";
|
||||
protected static const HEADER_CHUNK :String = "IHDR";
|
||||
protected static const COLOR_TYPE_IDX :int = 9;
|
||||
protected static const INDEXED_COLOR_TYPE :int = 3;
|
||||
|
||||
protected static var _crcTable :Array;
|
||||
}
|
||||
}
|
||||
|
||||
import com.threerings.util.StringUtil;
|
||||
|
||||
/** Used for recoloring pngs. */
|
||||
class PngChunk
|
||||
{
|
||||
/** The index of the beginning of the data section of the chunk. */
|
||||
public var idx :int;
|
||||
|
||||
/** The length of the data section of the chunk. */
|
||||
public var length :int;
|
||||
|
||||
/** The type of this chunk. */
|
||||
public var type :String;
|
||||
|
||||
public function PngChunk (idx :int, length :int, type :String)
|
||||
{
|
||||
this.idx = idx;
|
||||
this.length = length;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public function toString () :String
|
||||
{
|
||||
return StringUtil.simpleToString(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.media.tile {
|
||||
|
||||
public class BaseTile extends Tile
|
||||
{
|
||||
/**
|
||||
* Returns whether or not this tile can be walked upon by character
|
||||
* sprites.
|
||||
*/
|
||||
public function isPassable () :Boolean
|
||||
{
|
||||
return _passable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures this base tile as passable or impassable.
|
||||
*/
|
||||
public function setPassable (passable :Boolean) :void
|
||||
{
|
||||
_passable = passable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the x offset into the tile image of the origin (which will be aligned with the
|
||||
* bottom center of the origin tile) or <code>Integer.MIN_VALUE</code> if the origin is not
|
||||
* explicitly specified and should be computed from the image size and tile footprint.
|
||||
*/
|
||||
public override function getOriginX () :int
|
||||
{
|
||||
return getWidth()/2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the y offset into the tile image of the origin (which will be aligned with the
|
||||
* bottom center of the origin tile) or <code>Integer.MIN_VALUE</code> if the origin is not
|
||||
* explicitly specified and should be computed from the image size and tile footprint.
|
||||
*/
|
||||
public override function getOriginY () :int
|
||||
{
|
||||
return getHeight();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the object footprint width in tile units.
|
||||
*/
|
||||
public override function getBaseWidth () :int
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object footprint height in tile units.
|
||||
*/
|
||||
public override function getBaseHeight () :int
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/** Whether the tile is passable. */
|
||||
protected var _passable :Boolean = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.media.tile {
|
||||
|
||||
import com.threerings.util.Arrays;
|
||||
import com.threerings.util.StringUtil;
|
||||
|
||||
public class BaseTileSet extends SwissArmyTileSet
|
||||
{
|
||||
/**
|
||||
* Sets the passability information for the tiles in this tileset.
|
||||
* Each entry in the array corresponds to the tile at that tile index.
|
||||
*/
|
||||
public function setPassability (passable :Array) :void
|
||||
{
|
||||
_passable = passable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the passability information for the tiles in this tileset.
|
||||
*/
|
||||
public function getPassability () :Array
|
||||
{
|
||||
return _passable;
|
||||
}
|
||||
|
||||
override protected function createTile () :Tile
|
||||
{
|
||||
return new BaseTile();
|
||||
}
|
||||
|
||||
override protected function initTile (tile :Tile, tileIndex :int, zations :Array) :void
|
||||
{
|
||||
super.initTile(tile, tileIndex, zations);
|
||||
(BaseTile(tile)).setPassable(_passable[tileIndex]);
|
||||
}
|
||||
|
||||
override protected function toStringBuf (buf :String) :String
|
||||
{
|
||||
buf = super.toStringBuf(buf);
|
||||
buf = buf.concat(", passable=", StringUtil.toString(_passable));
|
||||
return buf;
|
||||
}
|
||||
|
||||
public static function fromXml (xml :XML) :SwissArmyTileSet
|
||||
{
|
||||
var set :BaseTileSet = new BaseTileSet();
|
||||
set.populateFromXml(xml);
|
||||
return set;
|
||||
}
|
||||
|
||||
override protected function populateFromXml (xml :XML) :void
|
||||
{
|
||||
super.populateFromXml(xml);
|
||||
_passable = toBoolArray(xml.passable);
|
||||
}
|
||||
|
||||
override public function populateClone (clone :TileSet) :void
|
||||
{
|
||||
super.populateClone(clone);
|
||||
var bClone :BaseTileSet = BaseTileSet(clone);
|
||||
bClone._passable = _passable == null ? null : Arrays.copyOf(_passable);
|
||||
}
|
||||
|
||||
override public function createClone () :TileSet
|
||||
{
|
||||
return new BaseTileSet();
|
||||
}
|
||||
|
||||
/** Whether each tile is passable. */
|
||||
protected var _passable :Array;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.media.tile {
|
||||
|
||||
import com.threerings.media.image.Colorization;
|
||||
|
||||
public interface Colorizer
|
||||
{
|
||||
/**
|
||||
* Returns the colorization to be used for the specified named colorization class.
|
||||
*
|
||||
* @param index the index of the colorization being requested in the tileset's colorization
|
||||
* list.
|
||||
*/
|
||||
function getColorization (index :int, zation :String) :Colorization;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.media.tile {
|
||||
|
||||
import nochump.util.zip.ZipEntry;
|
||||
|
||||
import flash.net.URLLoader;
|
||||
import flash.net.URLLoaderDataFormat;
|
||||
import flash.net.URLRequest;
|
||||
import flash.events.Event;
|
||||
import flash.utils.ByteArray;
|
||||
|
||||
import com.threerings.io.ObjectInputStream;
|
||||
import com.threerings.media.tile.TileDataPack;
|
||||
import com.threerings.media.tile.bundle.TileSetBundle;
|
||||
import com.threerings.util.DataPack;
|
||||
import com.threerings.util.Integer;
|
||||
import com.threerings.util.Log;
|
||||
import com.threerings.util.Maps;
|
||||
import com.threerings.util.Map;
|
||||
import com.threerings.util.Set;
|
||||
import com.threerings.util.StringUtil;
|
||||
|
||||
public class DataPackTileSetRepository
|
||||
implements TileSetRepository, TileSetIdMap
|
||||
{
|
||||
private static var log :Log = Log.getLog(DataPackTileSetRepository);
|
||||
|
||||
public function DataPackTileSetRepository (tileSetUrl :String)
|
||||
{
|
||||
_rootUrl = tileSetUrl;
|
||||
loadTileSetMeta(tileSetUrl + "metadata.jar");
|
||||
}
|
||||
|
||||
protected function loadTileSetMeta (tileSetMetaUrl :String) :void
|
||||
{
|
||||
_metaPack = new TileDataPack(tileSetMetaUrl, metaLoadingComplete);
|
||||
}
|
||||
|
||||
protected function parseTileSetMap (map :String) :void
|
||||
{
|
||||
var lines :Array = map.split("\n");
|
||||
var tileCount :int = int(lines[0]);
|
||||
for (var ii :int = 1; ii < lines.length; ii++) {
|
||||
var line :String = lines[ii];
|
||||
var toks :Array = line.split(" := ");
|
||||
if (toks.length >= 2) {
|
||||
var id :int = int(toks[1]);
|
||||
var name :String = String(toks[0]);
|
||||
_nameMap.put(name, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the successful completion of datapack loading.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
protected function metaLoadingComplete (event :Event) :void
|
||||
{
|
||||
parseTileSetMap(_metaPack.getFileAsString("tilesetmap.txt"));
|
||||
|
||||
for each (var entry :ZipEntry in _metaPack.getFiles()) {
|
||||
var bundleName :String = entry.name;
|
||||
if (StringUtil.endsWith(bundleName, "/bundle.xml")) {
|
||||
loadTileSetsFromPack(bundleName.substr(0, bundleName.length - 11));
|
||||
}
|
||||
}
|
||||
|
||||
// Notify them all and clear our list.
|
||||
for each (var func :Function in _notifyOnLoad) {
|
||||
func();
|
||||
}
|
||||
_notifyOnLoad = [];
|
||||
}
|
||||
|
||||
|
||||
public function notifyOnLoad (func :Function) :void
|
||||
{
|
||||
_notifyOnLoad.push(func);
|
||||
}
|
||||
|
||||
public function enumerateTileSetIds () :Array
|
||||
{
|
||||
return _idMap.keys();
|
||||
}
|
||||
|
||||
public function enumerateTileSets () :Array
|
||||
{
|
||||
return _idMap.values();
|
||||
}
|
||||
|
||||
public function getTileSet (tileSetId :int) :TileSet
|
||||
{
|
||||
var tileSet :TileSet = _idMap.get(tileSetId);
|
||||
|
||||
// We don't have this tileset loaded just yet.... start grabbing the data pack.
|
||||
var packName :String = _packMap.get(tileSetId);
|
||||
if (packName == null) {
|
||||
log.warning("Attempted to load unknown tile set", "tileSetId", tileSetId);
|
||||
throw new NoSuchTileSetError(tileSetId);
|
||||
}
|
||||
|
||||
var pack :TileDataPack = _packs.get(packName);
|
||||
|
||||
if (pack == null || !pack.isComplete()) {
|
||||
loadPack(packName, tileSetId, tileSet);
|
||||
|
||||
// Loading the pack should've put us in the map, so let's go retrieve it...
|
||||
tileSet = _idMap.get(tileSetId);
|
||||
} else {
|
||||
tileSet.setImageProvider(_packs.get(packName));
|
||||
}
|
||||
|
||||
return tileSet;
|
||||
}
|
||||
|
||||
protected function loadPack (packName :String, tileSetId :int, tileSet :TileSet) :void
|
||||
{
|
||||
var pack :TileDataPack = _packs.get(packName);
|
||||
|
||||
if (pack != null && pack.isComplete()) {
|
||||
// Already loaded
|
||||
return;
|
||||
}
|
||||
|
||||
var completeListener :Function = function () :void {
|
||||
tileSet.setImageProvider(_packs.get(packName));
|
||||
};
|
||||
|
||||
var listeners :Array = _packListeners.get(packName);
|
||||
if (listeners == null) {
|
||||
listeners = [];
|
||||
_packListeners.put(packName, listeners);
|
||||
|
||||
var packCompleteListener :Function = function () :void {
|
||||
for each (var listener :Function in listeners) {
|
||||
listener();
|
||||
}
|
||||
_packListeners.remove(packName);
|
||||
}
|
||||
|
||||
pack = new TileDataPack(getPackUrl(packName), packCompleteListener);
|
||||
_packs.put(packName, pack);
|
||||
}
|
||||
|
||||
listeners.push(completeListener);
|
||||
}
|
||||
|
||||
protected function loadTileSetsFromPack (packName :String) :void
|
||||
{
|
||||
var xml :XML = _metaPack.getFileAsXML(packName + "/bundle.xml");
|
||||
var bundle :TileSetBundle = TileSetBundle.fromXml(xml, this);
|
||||
bundle.forEach(function(key :*, val :*) :void {
|
||||
_idMap.put(key, val);
|
||||
_packMap.put(key, packName);
|
||||
});
|
||||
}
|
||||
|
||||
protected function getPackUrl (packName :String) :String
|
||||
{
|
||||
return _rootUrl + packName + "/bundle.jar";
|
||||
}
|
||||
|
||||
public function getTileSetId (setName :String) :int
|
||||
{
|
||||
return _nameMap.get(setName);
|
||||
}
|
||||
|
||||
public function getTileSetByName (setName :String) :TileSet
|
||||
{
|
||||
return getTileSet(getTileSetId(setName));
|
||||
}
|
||||
|
||||
protected function getPackName (name :String) :String
|
||||
{
|
||||
var lastSlashIdx :int = name.lastIndexOf("/");
|
||||
if (lastSlashIdx == -1) {
|
||||
return name;
|
||||
} else {
|
||||
return name.substring(0, lastSlashIdx).toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
public function ensureLoaded (tileSets :Set, completeCallback :Function,
|
||||
progressCallback :Function) :void
|
||||
{
|
||||
var completeCt :int = 0;
|
||||
|
||||
var size :int = tileSets.size();
|
||||
|
||||
// Handles the completion of a single tileset, which if it's our last may call the callback.
|
||||
function completeOneSet () :void {
|
||||
completeCt++;
|
||||
|
||||
progressCallback(completeCt / Number(size));
|
||||
|
||||
if (completeCt >= tileSets.size()) {
|
||||
completeCallback();
|
||||
|
||||
// Ensure we stop making callbacks.
|
||||
completeCallback = null;
|
||||
progressCallback = null;
|
||||
}
|
||||
};
|
||||
|
||||
tileSets.forEach(function (setId :int) :void {
|
||||
var thisSet :TileSet = getTileSet(setId);
|
||||
if (thisSet.isLoaded()) {
|
||||
completeOneSet();
|
||||
} else {
|
||||
thisSet.notifyOnLoad(completeOneSet);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Contains the tileset meta-data for all tilesets. */
|
||||
protected var _metaPack : DataPack;
|
||||
|
||||
/** Maps name to tileSetId. */
|
||||
protected var _nameMap :Map = Maps.newMapOf(String);
|
||||
|
||||
/** Maps tileSetId to actual TileSet. */
|
||||
protected var _idMap : Map = Maps.newMapOf(int);
|
||||
|
||||
/** Maps tileSetId to DataPack name. */
|
||||
protected var _packMap :Map = Maps.newMapOf(int);
|
||||
|
||||
/** The URL for our tilesets. */
|
||||
protected var _rootUrl :String;
|
||||
|
||||
/** Map of pack name to listeners waiting for that pack to resolve. */
|
||||
protected var _packListeners :Map = Maps.newMapOf(String);
|
||||
|
||||
/** The map of packs we've already loaded up and resolved. */
|
||||
protected var _packs :Map = Maps.newMapOf(String);
|
||||
|
||||
/** Everyone who cares when we're loaded. */
|
||||
protected var _notifyOnLoad :Array = [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.media.tile {
|
||||
|
||||
import flash.display.DisplayObject;
|
||||
import flash.geom.Rectangle;
|
||||
|
||||
public interface ImageProvider
|
||||
{
|
||||
/**
|
||||
* Returns the raw tileset image with the specified path.
|
||||
*
|
||||
* @param path the path that identifies the desired image (corresponds to the image path from
|
||||
* the tileset).
|
||||
* @param zations if non-null, colorizations to apply to the source image before returning it.
|
||||
*/
|
||||
function getTileSetImage (path :String, zations :Array, callback :Function) :void;
|
||||
|
||||
/**
|
||||
* Obtains the tile image with the specified path in the form of a {@link Mirage}. It should be
|
||||
* cropped from the tileset image identified by the supplied path.
|
||||
*
|
||||
* @param path the path that identifies the desired image (corresponds to the image path from
|
||||
* the tileset).
|
||||
* @param bounds if non-null, the region of the image to be returned as a mirage. If null, the
|
||||
* entire image should be returned.
|
||||
* @param zations if non-null, colorizations to apply to the image before converting it into a
|
||||
* mirage.
|
||||
*/
|
||||
function getTileImage (path :String, bounds :Rectangle, zations :Array, callback :Function)
|
||||
:void;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.media.tile {
|
||||
|
||||
public class NoSuchTileSetError extends Error
|
||||
{
|
||||
public function NoSuchTileSetError (tileSet :*)
|
||||
{
|
||||
super((tileSet is int) ? ("No tile set with id '" + int(tileSet) + "'") :
|
||||
("No tile set named'" + String(tileSet) + "'"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.media.tile {
|
||||
|
||||
import flash.geom.Point;
|
||||
|
||||
import com.threerings.media.tile.Tile;
|
||||
import com.threerings.util.Arrays;
|
||||
import com.threerings.util.DirectionUtil;
|
||||
import com.threerings.util.Integer;
|
||||
import com.threerings.util.StringUtil;
|
||||
|
||||
public class ObjectTile extends Tile
|
||||
{
|
||||
/**
|
||||
* Returns the object footprint width in tile units.
|
||||
*/
|
||||
public override function getBaseWidth () :int
|
||||
{
|
||||
return _base.x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object footprint height in tile units.
|
||||
*/
|
||||
public override function getBaseHeight () :int
|
||||
{
|
||||
return _base.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the object footprint in tile units.
|
||||
*/
|
||||
public function setBase (width :int, height :int) :void
|
||||
{
|
||||
_base.x = width;
|
||||
_base.y = height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the x offset into the tile image of the origin (which will be aligned with the
|
||||
* bottom center of the origin tile) or <code>Integer.MIN_VALUE</code> if the origin is not
|
||||
* explicitly specified and should be computed from the image size and tile footprint.
|
||||
*/
|
||||
public override function getOriginX () :int
|
||||
{
|
||||
return _origin.x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the y offset into the tile image of the origin (which will be aligned with the
|
||||
* bottom center of the origin tile) or <code>Integer.MIN_VALUE</code> if the origin is not
|
||||
* explicitly specified and should be computed from the image size and tile footprint.
|
||||
*/
|
||||
public override function getOriginY () :int
|
||||
{
|
||||
return _origin.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the offset in pixels from the origin of the tile image to the origin of the object.
|
||||
* The object will be rendered such that its origin is at the bottom center of its origin
|
||||
* tile. If no origin is specified, the bottom of the image is aligned with the bottom of the
|
||||
* origin tile and the left side of the image is aligned with the left edge of the left-most
|
||||
* base tile.
|
||||
*/
|
||||
public function setOrigin (x :int, y :int) :void
|
||||
{
|
||||
_origin.x = x;
|
||||
_origin.y = y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this object tile's default render priority.
|
||||
*/
|
||||
public function getPriority () :int
|
||||
{
|
||||
return _priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this object tile's default render priority.
|
||||
*/
|
||||
public function setPriority (priority :int) :void
|
||||
{
|
||||
_priority = priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the "spot" associated with this object.
|
||||
*/
|
||||
public function setSpot (x :int, y :int, orient :int) :void
|
||||
{
|
||||
_spot = new Point(x, y);
|
||||
_sorient = orient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this object has a spot.
|
||||
*/
|
||||
public function hasSpot () :Boolean
|
||||
{
|
||||
return (_spot != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the x-coordinate of the "spot" associated with this object.
|
||||
*/
|
||||
public function getSpotX () :int
|
||||
{
|
||||
return (_spot == null) ? 0 : _spot.x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the x-coordinate of the "spot" associated with this object.
|
||||
*/
|
||||
public function getSpotY () :int
|
||||
{
|
||||
return (_spot == null) ? 0 : _spot.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the orientation of the "spot" associated with this object.
|
||||
*/
|
||||
public function getSpotOrient () :int
|
||||
{
|
||||
return _sorient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of constraints associated with this object, or <code>null</code> if the
|
||||
* object has no constraints.
|
||||
*/
|
||||
public function getConstraints () :Array
|
||||
{
|
||||
return _constraints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this object has the given constraint.
|
||||
*/
|
||||
public function hasConstraint (constraint :String) :Boolean
|
||||
{
|
||||
return (_constraints == null) ? false :
|
||||
Arrays.contains(_constraints, constraint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures this object's constraints.
|
||||
*/
|
||||
public function setConstraints (constraints :Array) :void
|
||||
{
|
||||
_constraints = constraints;
|
||||
}
|
||||
|
||||
protected override function toStringBuf (buf :String) :String
|
||||
{
|
||||
buf = super.toStringBuf(buf);
|
||||
buf.concat(", base=", StringUtil.toString(_base));
|
||||
buf.concat(", origin=", StringUtil.toString(_origin));
|
||||
buf.concat(", priority=", _priority);
|
||||
if (_spot != null) {
|
||||
buf.concat(", spot=", StringUtil.toString(_spot));
|
||||
buf.concat(", sorient=");
|
||||
buf.concat(DirectionUtil.toShortString(_sorient));
|
||||
}
|
||||
if (_constraints != null) {
|
||||
buf.concat(", constraints=", StringUtil.toString(_constraints));
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
/** The object footprint width in unit tile units. */
|
||||
protected var _base :Point = new Point(1, 1);
|
||||
|
||||
/** The offset from the origin of the tile image to the object's origin or MIN_VALUE if the
|
||||
* origin should be calculated based on the footprint. */
|
||||
protected var _origin :Point = new Point(Integer.MIN_VALUE, Integer.MIN_VALUE);
|
||||
|
||||
/** This object tile's default render priority. */
|
||||
protected var _priority :int;
|
||||
|
||||
/** The coordinates of the "spot" associated with this object. */
|
||||
protected var _spot :Point;
|
||||
|
||||
/** The orientation of the "spot" associated with this object. */
|
||||
protected var _sorient :int;
|
||||
|
||||
/** The list of constraints associated with this object. */
|
||||
protected var _constraints :Array;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.media.tile {
|
||||
|
||||
import com.threerings.util.Arrays;
|
||||
import com.threerings.util.StringUtil;
|
||||
|
||||
/**
|
||||
* The object tileset supports the specification of object information for object tiles in
|
||||
* addition to all of the features of the swiss army tileset.
|
||||
*
|
||||
* @see ObjectTile
|
||||
*/
|
||||
public class ObjectTileSet extends SwissArmyTileSet
|
||||
implements RecolorableTileSet
|
||||
{
|
||||
/** A constraint prefix indicating that the object must have empty space in
|
||||
* the suffixed direction (N, E, S, or W). */
|
||||
public static const SPACE :String = "SPACE_";
|
||||
|
||||
/** A constraint indicating that the object is a surface (e.g., table). */
|
||||
public static const SURFACE :String = "SURFACE";
|
||||
|
||||
/** A constraint indicating that the object must be placed on a surface. */
|
||||
public static const ON_SURFACE :String = "ON_SURFACE";
|
||||
|
||||
/** A constraint prefix indicating that the object is a wall facing the
|
||||
* suffixed direction (N, E, S, or W). */
|
||||
public static const WALL :String = "WALL_";
|
||||
|
||||
/** A constraint prefix indicating that the object must be placed on a
|
||||
* wall facing the suffixed direction (N, E, S, or W). */
|
||||
public static const ON_WALL :String = "ON_WALL_";
|
||||
|
||||
/** A constraint prefix indicating that the object must be attached to a
|
||||
* wall facing the suffixed direction (N, E, S, or W). */
|
||||
public static const ATTACH :String = "ATTACH_";
|
||||
|
||||
/** The low suffix for walls and attachments. Low attachments can be placed
|
||||
* on low or normal walls; normal attachments can only be placed on normal
|
||||
* walls. */
|
||||
public static const LOW :String = "_LOW";
|
||||
|
||||
/**
|
||||
* Sets the widths (in unit tile count) of the objects in this tileset. This must be
|
||||
* accompanied by a call to {@link #setObjectHeights}.
|
||||
*/
|
||||
public function setObjectWidths (objectWidths :Array) :void
|
||||
{
|
||||
_owidths = objectWidths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the heights (in unit tile count) of the objects in this tileset. This must be
|
||||
* accompanied by a call to {@link #setObjectWidths}.
|
||||
*/
|
||||
public function setObjectHeights (objectHeights :Array) :void
|
||||
{
|
||||
_oheights = objectHeights;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the x offset in pixels to the image origin.
|
||||
*/
|
||||
public function setXOrigins (xorigins :Array) :void
|
||||
{
|
||||
_xorigins = xorigins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the y offset in pixels to the image origin.
|
||||
*/
|
||||
public function setYOrigins (yorigins :Array) :void
|
||||
{
|
||||
_yorigins = yorigins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default render priorities for our object tiles.
|
||||
*/
|
||||
public function setPriorities (priorities :Array) :void
|
||||
{
|
||||
_priorities = priorities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a set of colorization classes that apply to objects in this tileset.
|
||||
*/
|
||||
public function setColorizations (zations :Array) :void
|
||||
{
|
||||
_ozations = zations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the x offset to the "spots" associated with our object tiles.
|
||||
*/
|
||||
public function setXSpots (xspots :Array) :void
|
||||
{
|
||||
_xspots = xspots;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the y offset to the "spots" associated with our object tiles.
|
||||
*/
|
||||
public function setYSpots (yspots :Array) :void
|
||||
{
|
||||
_yspots = yspots;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the orientation of the "spots" associated with our object
|
||||
* tiles.
|
||||
*/
|
||||
public function setSpotOrients (sorients :Array) :void
|
||||
{
|
||||
_sorients = sorients;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the lists of constraints associated with our object tiles.
|
||||
*/
|
||||
public function setConstraints (constraints :Array) :void
|
||||
{
|
||||
_constraints = constraints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the x coordinate of the spot associated with the specified tile index.
|
||||
*/
|
||||
public function getXSpot (tileIdx :int) :int
|
||||
{
|
||||
return (_xspots == null) ? 0 : _xspots[tileIdx];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the y coordinate of the spot associated with the specified tile index.
|
||||
*/
|
||||
public function getYSpot (tileIdx :int) :int
|
||||
{
|
||||
return (_yspots == null) ? 0 : _yspots[tileIdx];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the orientation of the spot associated with the specified tile index.
|
||||
*/
|
||||
public function getSpotOrient (tileIdx :int) :int
|
||||
{
|
||||
return (_sorients == null) ? 0 : _sorients[tileIdx];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of constraints associated with the specified tile index, or
|
||||
* <code>null</code> if the index has no constraints.
|
||||
*/
|
||||
public function getConstraints (tileIdx :int) :Array
|
||||
{
|
||||
return (_constraints == null) ? null : _constraints[tileIdx];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the tile at the specified index has the given constraint.
|
||||
*/
|
||||
public function hasConstraint (tileIdx :int, constraint :String) :Boolean
|
||||
{
|
||||
return (_constraints == null) ? false :
|
||||
Arrays.contains(_constraints[tileIdx], constraint);
|
||||
}
|
||||
|
||||
// documentation inherited from interface RecolorableTileSet
|
||||
public function getColorizations () :Array
|
||||
{
|
||||
return _ozations;
|
||||
}
|
||||
|
||||
override protected function toStringBuf (buf :String) :String
|
||||
{
|
||||
buf = super.toStringBuf(buf);
|
||||
buf = buf.concat(", owidths=", StringUtil.toString(_owidths));
|
||||
buf = buf.concat(", oheights=", StringUtil.toString(_oheights));
|
||||
buf = buf.concat(", xorigins=", StringUtil.toString(_xorigins));
|
||||
buf = buf.concat(", yorigins=", StringUtil.toString(_yorigins));
|
||||
buf = buf.concat(", prios=", StringUtil.toString(_priorities));
|
||||
buf = buf.concat(", zations=", StringUtil.toString(_ozations));
|
||||
buf = buf.concat(", xspots=", StringUtil.toString(_xspots));
|
||||
buf = buf.concat(", yspots=", StringUtil.toString(_yspots));
|
||||
buf = buf.concat(", sorients=", StringUtil.toString(_sorients));
|
||||
buf = buf.concat(", constraints=", StringUtil.toString(_constraints));
|
||||
return buf;
|
||||
}
|
||||
|
||||
override protected function getTileColorizations (tileIndex :int, rizer :Colorizer) :Array
|
||||
{
|
||||
var zations :Array = null;
|
||||
if (rizer != null && _ozations != null) {
|
||||
zations = new Array(_ozations.length);
|
||||
for (var ii :int = 0; ii < _ozations.length; ii++) {
|
||||
zations[ii] = rizer.getColorization(ii, _ozations[ii]);
|
||||
}
|
||||
}
|
||||
return zations;
|
||||
}
|
||||
|
||||
override protected function createTile () :Tile
|
||||
{
|
||||
return new ObjectTile();
|
||||
}
|
||||
|
||||
override protected function initTile (tile :Tile, tileIndex :int, zations :Array) :void
|
||||
{
|
||||
super.initTile(tile, tileIndex, zations);
|
||||
|
||||
var otile :ObjectTile = ObjectTile(tile);
|
||||
if (_owidths != null) {
|
||||
otile.setBase(_owidths[tileIndex], _oheights[tileIndex]);
|
||||
}
|
||||
if (_xorigins != null) {
|
||||
otile.setOrigin(_xorigins[tileIndex], _yorigins[tileIndex]);
|
||||
}
|
||||
if (_priorities != null) {
|
||||
otile.setPriority(_priorities[tileIndex]);
|
||||
}
|
||||
if (_xspots != null) {
|
||||
otile.setSpot(_xspots[tileIndex], _yspots[tileIndex], _sorients[tileIndex]);
|
||||
}
|
||||
if (_constraints != null) {
|
||||
otile.setConstraints(_constraints[tileIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
public static function fromXml (xml :XML) :SwissArmyTileSet
|
||||
{
|
||||
var set :ObjectTileSet = new ObjectTileSet();
|
||||
set.populateFromXml(xml);
|
||||
return set;
|
||||
}
|
||||
|
||||
override protected function populateFromXml (xml :XML) :void
|
||||
{
|
||||
super.populateFromXml(xml);
|
||||
_owidths = toIntArray(xml.objectWidths);
|
||||
_oheights = toIntArray(xml.objectHeights);
|
||||
_xorigins = toIntArray(xml.xOrigins);
|
||||
_yorigins = toIntArray(xml.yOrigins);
|
||||
_priorities = toIntArray(xml.priorities);
|
||||
_ozations = toStrArray(xml.zations);
|
||||
_xspots = toIntArray(xml.xSpots);
|
||||
_yspots = toIntArray(xml.ySpots);
|
||||
_sorients = toIntArray(xml.sorients);
|
||||
var constraintStrArr :Array = toStrArray(xml.constraints);
|
||||
if (constraintStrArr != null) {
|
||||
_constraints =
|
||||
constraintStrArr.map(function(element :*, index :int, arr :Array) :Array {
|
||||
return element.split("|");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
override public function populateClone (clone :TileSet) :void
|
||||
{
|
||||
super.populateClone(clone);
|
||||
var oClone :ObjectTileSet = ObjectTileSet(clone);
|
||||
oClone._owidths = _owidths == null ? null : Arrays.copyOf(_owidths);
|
||||
oClone._oheights = _oheights == null ? null : Arrays.copyOf(_oheights);
|
||||
oClone._xorigins = _xorigins == null ? null : Arrays.copyOf(_xorigins);
|
||||
oClone._yorigins = _yorigins == null ? null : Arrays.copyOf(_yorigins);
|
||||
oClone._priorities = _priorities == null ? null : Arrays.copyOf(_priorities);
|
||||
oClone._ozations = _ozations == null ? null : Arrays.copyOf(_ozations);
|
||||
oClone._xspots = _xspots == null ? null : Arrays.copyOf(_xspots);
|
||||
oClone._yspots = _yspots == null ? null : Arrays.copyOf(_yspots);
|
||||
oClone._sorients = _sorients == null ? null : Arrays.copyOf(_sorients);
|
||||
oClone._constraints = _constraints == null ? null : Arrays.copyOf(_constraints);
|
||||
}
|
||||
|
||||
override public function createClone () :TileSet
|
||||
{
|
||||
return new ObjectTileSet();
|
||||
}
|
||||
|
||||
/** The width (in tile units) of our object tiles. */
|
||||
protected var _owidths :Array;
|
||||
|
||||
/** The height (in tile units) of our object tiles. */
|
||||
protected var _oheights :Array;
|
||||
|
||||
/** The x offset in pixels to the origin of the tile images. */
|
||||
protected var _xorigins :Array;
|
||||
|
||||
/** The y offset in pixels to the origin of the tile images. */
|
||||
protected var _yorigins :Array;
|
||||
|
||||
/** The default render priorities of our objects. */
|
||||
protected var _priorities :Array;
|
||||
|
||||
/** Colorization classes that apply to our objects. */
|
||||
protected var _ozations :Array;
|
||||
|
||||
/** The x offset to the "spots" associated with our tiles. */
|
||||
protected var _xspots :Array;
|
||||
|
||||
/** The y offset to the "spots" associated with our tiles. */
|
||||
protected var _yspots :Array;
|
||||
|
||||
/** The orientation of the "spots" associated with our tiles. */
|
||||
protected var _sorients :Array;
|
||||
|
||||
/** Lists of constraints associated with our tiles. */
|
||||
protected var _constraints :Array;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.media.tile {
|
||||
|
||||
public interface RecolorableTileSet
|
||||
{
|
||||
/**
|
||||
* Returns the colorization classes that should be used to recolor
|
||||
* objects in this tileset.
|
||||
*/
|
||||
function getColorizations () :Array
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.media.tile {
|
||||
|
||||
import flash.geom.Point;
|
||||
import flash.geom.Rectangle;
|
||||
|
||||
import com.threerings.util.Arrays;
|
||||
import com.threerings.util.StringUtil;
|
||||
|
||||
/**
|
||||
* The swiss army tileset supports a diverse variety of tiles in the tileset image. Each row can
|
||||
* contain varying numbers of tiles and each row can have its own width and height. Tiles can be
|
||||
* separated from the edge of the tileset image by some border offset and can be separated from one
|
||||
* another by a gap distance.
|
||||
*/
|
||||
public class SwissArmyTileSet extends TileSet
|
||||
{
|
||||
public override function getTileCount () :int
|
||||
{
|
||||
return _numTiles;
|
||||
}
|
||||
|
||||
public override function computeTileBounds (tileIndex :int) :Rectangle
|
||||
{
|
||||
// find the row number containing the sought-after tile
|
||||
var ridx :int;
|
||||
var tcount :int;
|
||||
var ty :int;
|
||||
var tx :int;
|
||||
ridx = tcount = 0;
|
||||
|
||||
// start tile image position at image start offset
|
||||
tx = _offsetPos.x;
|
||||
ty = _offsetPos.y;
|
||||
|
||||
while ((tcount += _tileCounts[ridx]) < tileIndex + 1) {
|
||||
// increment tile image position by row height and gap distance
|
||||
ty += (_heights[ridx++] + _gapSize.y);
|
||||
}
|
||||
|
||||
// determine the horizontal index of this tile in the row
|
||||
var xidx :int = tileIndex - (tcount - _tileCounts[ridx]);
|
||||
|
||||
// final image x-position is based on tile width and gap distance
|
||||
tx += (xidx * (_widths[ridx] + _gapSize.x));
|
||||
|
||||
// Log.info("Computed tile bounds [tileIndex=" + tileIndex +
|
||||
// ", ridx=" + ridx + ", xidx=" + xidx + ", tx=" + tx + ", ty=" + ty + "].");
|
||||
|
||||
// crop the tile-sized image chunk from the full image
|
||||
return new Rectangle(tx, ty, _widths[ridx], _heights[ridx]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the tile counts which are the number of tiles in each row of the tileset image. Each
|
||||
* row can have an arbitrary number of tiles.
|
||||
*/
|
||||
public function setTileCounts (tileCounts :Array) :void
|
||||
{
|
||||
_tileCounts = tileCounts;
|
||||
|
||||
// compute our total tile count
|
||||
computeTileCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the tile count settings.
|
||||
*/
|
||||
public function getTileCounts () :Array
|
||||
{
|
||||
return _tileCounts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes our total tile count from the individual counts for each row.
|
||||
*/
|
||||
protected function computeTileCount () :void
|
||||
{
|
||||
// compute our number of tiles
|
||||
_numTiles = 0;
|
||||
for each (var count :int in _tileCounts) {
|
||||
_numTiles += count;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the tile widths for each row. Each row can have tiles of a different width.
|
||||
*/
|
||||
public function setWidths (widths :Array) :void
|
||||
{
|
||||
_widths = widths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the width settings.
|
||||
*/
|
||||
public function getWidths () :Array
|
||||
{
|
||||
return _widths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the tile heights for each row. Each row can have tiles of a different height.
|
||||
*/
|
||||
public function setHeights (heights :Array) :void
|
||||
{
|
||||
_heights = heights;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the height settings.
|
||||
*/
|
||||
public function getHeights () :Array
|
||||
{
|
||||
return _heights;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the offset in pixels of the upper left corner of the first tile in the first row. If
|
||||
* the tileset image has a border, this can be set to account for it.
|
||||
*/
|
||||
public function setOffsetPos (offsetPos :Point) :void
|
||||
{
|
||||
_offsetPos = offsetPos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the size of the gap between tiles (in pixels). If the tiles have space between them,
|
||||
* this can be set to account for it.
|
||||
*/
|
||||
public function setGapSize (gapSize :Point) :void
|
||||
{
|
||||
_gapSize = gapSize;
|
||||
}
|
||||
|
||||
protected override function toStringBuf (buf :String) :String
|
||||
{
|
||||
buf = super.toStringBuf(buf);
|
||||
buf = buf.concat(", widths=", StringUtil.toString(_widths));
|
||||
buf = buf.concat(", heights=", StringUtil.toString(_heights));
|
||||
buf = buf.concat(", tileCounts=", StringUtil.toString(_tileCounts));
|
||||
buf = buf.concat(", offsetPos=", StringUtil.toString(_offsetPos));
|
||||
buf = buf.concat(", gapSize=", StringUtil.toString(_gapSize));
|
||||
return buf;
|
||||
}
|
||||
|
||||
public static function fromXml (xml :XML) :SwissArmyTileSet
|
||||
{
|
||||
var set :SwissArmyTileSet = new SwissArmyTileSet();
|
||||
set.populateFromXml(xml);
|
||||
return set;
|
||||
}
|
||||
|
||||
protected override function populateFromXml (xml :XML) :void
|
||||
{
|
||||
super.populateFromXml(xml);
|
||||
_tileCounts = toIntArray(xml.tileCounts);
|
||||
_widths = toIntArray(xml.widths);
|
||||
_heights = toIntArray(xml.heights);
|
||||
var offsetPosArr :Array = toIntArray(xml.offsetPos);
|
||||
if (offsetPosArr != null) {
|
||||
_offsetPos = new Point(offsetPosArr[0], offsetPosArr[1]);
|
||||
} else {
|
||||
_offsetPos = new Point(0, 0);
|
||||
}
|
||||
var gapSizeArr :Array = toIntArray(xml.gapSize);
|
||||
if (gapSizeArr != null) {
|
||||
_gapSize = new Point(gapSizeArr[0], gapSizeArr[1]);
|
||||
} else {
|
||||
_gapSize = new Point(0, 0);
|
||||
}
|
||||
computeTileCount();
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
protected static function toBoolArray (str :String) :Array
|
||||
{
|
||||
if (str == null || str.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return str.split(",").map(function(element :*, index :int, arr :Array) :Boolean {
|
||||
return (StringUtil.trim(element) == "1");
|
||||
});
|
||||
}
|
||||
|
||||
protected static function toIntArray (str :String) :Array
|
||||
{
|
||||
if (str == null || str.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return str.split(",").map(function(element :*, index :int, arr :Array) :int {
|
||||
return int(StringUtil.trim(element));
|
||||
});
|
||||
}
|
||||
|
||||
override public function populateClone (clone :TileSet) :void
|
||||
{
|
||||
super.populateClone(clone);
|
||||
var saClone :SwissArmyTileSet = SwissArmyTileSet(clone);
|
||||
saClone._tileCounts = _tileCounts == null ? null : Arrays.copyOf(_tileCounts);
|
||||
saClone._numTiles = _numTiles;
|
||||
saClone._widths = _widths == null ? null : Arrays.copyOf(_widths);
|
||||
saClone._heights = _heights == null ? null : Arrays.copyOf(_heights);
|
||||
saClone._offsetPos = _offsetPos;
|
||||
saClone._gapSize = _gapSize;
|
||||
}
|
||||
|
||||
override public function createClone () :TileSet
|
||||
{
|
||||
return new SwissArmyTileSet();
|
||||
}
|
||||
|
||||
/** The number of tiles in each row. */
|
||||
protected var _tileCounts :Array;
|
||||
|
||||
/** The number of tiles in the tileset. */
|
||||
protected var _numTiles :int;
|
||||
|
||||
/** The width of the tiles in each row in pixels. */
|
||||
protected var _widths :Array;
|
||||
|
||||
/** The height of the tiles in each row in pixels. */
|
||||
protected var _heights :Array;
|
||||
|
||||
/** The offset distance (x, y) in pixels from the top-left of the image to the start of the
|
||||
* first tile image. */
|
||||
protected var _offsetPos :Point = new Point();
|
||||
|
||||
/** The distance (x, y) in pixels between each tile in each row horizontally, and between each
|
||||
* row of tiles vertically. */
|
||||
protected var _gapSize :Point = new Point();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.media.tile {
|
||||
|
||||
import flash.display.Bitmap;
|
||||
import flash.display.BitmapData;
|
||||
import flash.display.DisplayObject;
|
||||
|
||||
public class Tile
|
||||
{
|
||||
/** The key associated with this tile. */
|
||||
public var key :Tile_Key;
|
||||
|
||||
/**
|
||||
* Configures this tile with its tile image.
|
||||
*/
|
||||
public function setImage (image :DisplayObject) :void
|
||||
{
|
||||
_image = image;
|
||||
|
||||
// Notify them all and clear our list.
|
||||
for each (var func :Function in _notifyOnLoad) {
|
||||
func(this);
|
||||
}
|
||||
}
|
||||
|
||||
public function getImage () :DisplayObject
|
||||
{
|
||||
if (_image is Bitmap) {
|
||||
// TODO - handle this more consistently...
|
||||
return new Bitmap(Bitmap(_image).bitmapData);
|
||||
} else {
|
||||
return _image;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the width of this tile.
|
||||
*/
|
||||
public function getWidth () :int
|
||||
{
|
||||
return _image.width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the height of this tile.
|
||||
*/
|
||||
public function getHeight () :int
|
||||
{
|
||||
return _image.height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified coordinates within this tile contains
|
||||
* a non-transparent pixel.
|
||||
*/
|
||||
public function hitTest (x :int, y :int) :Boolean
|
||||
{
|
||||
return _image.hitTestPoint(x, y, true);
|
||||
}
|
||||
|
||||
public function toString () :String
|
||||
{
|
||||
return "[" + toStringBuf(new String()) + "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* This should be overridden by derived classes (which should be sure
|
||||
* to call <code>super.toString()</code>) to append the derived class
|
||||
* specific tile information to the string buffer.
|
||||
*/
|
||||
protected function toStringBuf (buf :String) :String
|
||||
{
|
||||
if (_image == null) {
|
||||
return buf.concat("null-image");
|
||||
} else {
|
||||
return buf.concat(_image.width, "x", _image.height);
|
||||
}
|
||||
}
|
||||
|
||||
public function notifyOnLoad (func :Function) :void
|
||||
{
|
||||
if (_image != null) {
|
||||
func(this);
|
||||
} else {
|
||||
_notifyOnLoad.push(func);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the x offset into the tile image of the origin (which will be aligned with the
|
||||
* bottom center of the origin tile) or <code>Integer.MIN_VALUE</code> if the origin is not
|
||||
* explicitly specified and should be computed from the image size and tile footprint.
|
||||
*/
|
||||
public function getOriginX () :int
|
||||
{
|
||||
throw new Error("abstract");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the y offset into the tile image of the origin (which will be aligned with the
|
||||
* bottom center of the origin tile) or <code>Integer.MIN_VALUE</code> if the origin is not
|
||||
* explicitly specified and should be computed from the image size and tile footprint.
|
||||
*/
|
||||
public function getOriginY () :int
|
||||
{
|
||||
throw new Error("abstract")
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object footprint width in tile units.
|
||||
*/
|
||||
public function getBaseWidth () :int
|
||||
{
|
||||
throw new Error("abstract");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object footprint height in tile units.
|
||||
*/
|
||||
public function getBaseHeight () :int
|
||||
{
|
||||
throw new Error("abstract");
|
||||
}
|
||||
|
||||
/** Our tileset image. */
|
||||
protected var _image :DisplayObject;
|
||||
|
||||
/** Everyone who cares when we're loaded. */
|
||||
protected var _notifyOnLoad :Array = [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.media.tile {
|
||||
|
||||
import flash.display.Bitmap;
|
||||
import flash.display.BitmapData;
|
||||
import flash.display.DisplayObject;
|
||||
|
||||
import flash.geom.Point;
|
||||
import flash.geom.Rectangle;
|
||||
|
||||
import flash.events.Event;
|
||||
|
||||
import flash.utils.ByteArray;
|
||||
|
||||
import nochump.util.zip.ZipEntry;
|
||||
import nochump.util.zip.ZipError;
|
||||
import nochump.util.zip.ZipFile;
|
||||
|
||||
import com.threerings.media.image.PngRecolorUtil;
|
||||
import com.threerings.util.DataPack;
|
||||
import com.threerings.util.Map;
|
||||
import com.threerings.util.Maps;
|
||||
import com.threerings.util.MultiLoader;
|
||||
import com.threerings.util.StringUtil;
|
||||
|
||||
/**
|
||||
* Like a normal data pack, but we don't deal with any data, and all our filenames are a direct
|
||||
* translation, so we need no metadata file. We also serve as a tile ImageProvider.
|
||||
*/
|
||||
public class TileDataPack extends DataPack
|
||||
implements ImageProvider
|
||||
{
|
||||
public function TileDataPack (
|
||||
source :Object, completeListener :Function = null, errorListener :Function = null)
|
||||
{
|
||||
super(source, completeListener, errorListener);
|
||||
}
|
||||
|
||||
protected override function bytesAvailable (bytes :ByteArray) :void
|
||||
{
|
||||
bytes.position = 0;
|
||||
try {
|
||||
_zip = new ZipFile(bytes);
|
||||
} catch (zipError :ZipError) {
|
||||
dispatchError("Unable to read datapack: " + zipError.message);
|
||||
return;
|
||||
}
|
||||
|
||||
// Put something there so we know we're loaded.
|
||||
_metadata = <xml></xml>;
|
||||
|
||||
// yay, we're completely loaded!
|
||||
dispatchEvent(new Event(Event.COMPLETE));
|
||||
}
|
||||
|
||||
/**
|
||||
* We have no metadata, so everything is undefined.
|
||||
*/
|
||||
public override function getData (name :String, formatType :String = null) :*
|
||||
{
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* We have no XML metadata, so our filename is exactly the original name.
|
||||
*/
|
||||
protected override function getFileName (name :String) :String
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
public function getTileSetImage (path :String, zations :Array, callback :Function) :void
|
||||
{
|
||||
var key :String = zations == null ? path : path + ":" + StringUtil.toString(zations);
|
||||
|
||||
if (_cache.containsKey(key)) {
|
||||
callback(_cache.get(key));
|
||||
} else if (_pending.containsKey(key)) {
|
||||
_pending.get(key).push(callback);
|
||||
} else {
|
||||
_pending.put(key, [callback]);
|
||||
MultiLoader.getContents(PngRecolorUtil.recolorPNG(getFile(path), zations),
|
||||
function(result :Bitmap) :void {
|
||||
_cache.put(key, result);
|
||||
for each (var func :Function in _pending.remove(key)) {
|
||||
func(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function getTileImage (path :String, bounds :Rectangle, zations :Array,
|
||||
callback :Function) :void
|
||||
{
|
||||
getTileSetImage(path, zations, function(result :Bitmap) :void {
|
||||
var data :BitmapData =
|
||||
new BitmapData(bounds.width, bounds.height, true, 0x00000000);
|
||||
data.copyPixels(Bitmap(result).bitmapData, bounds, new Point(0, 0));
|
||||
callback(new Bitmap(data));
|
||||
});
|
||||
}
|
||||
|
||||
/** Cache of images loaded from this data pack. */
|
||||
protected var _cache :Map = Maps.newMapOf(String);
|
||||
|
||||
/** Any images we're in the process of resolving, map their key to a list of listeners*/
|
||||
protected var _pending :Map = Maps.newMapOf(String);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.media.tile {
|
||||
|
||||
import com.threerings.util.Log;
|
||||
import com.threerings.util.Set;
|
||||
|
||||
/**
|
||||
* The tile manager provides a simplified interface for retrieving and caching tiles. Tiles can be
|
||||
* loaded in two different ways. An application can load a tileset by hand, specifying the path to
|
||||
* the tileset image and all of the tileset metadata necessary for extracting the image tiles, or
|
||||
* it can provide a tileset repository which loads up tilesets using whatever repository mechanism
|
||||
* is implemented by the supplied repository. In the latter case, tilesets are loaded by a unique
|
||||
* identifier.
|
||||
*
|
||||
* <p> Loading tilesets by hand is intended for things like toolbar icons or games with a single
|
||||
* set of tiles (think Stratego, for example). Loading tilesets from a repository supports games
|
||||
* with vast numbers of tiles to which more tiles may be added on the fly (think the tiles for an
|
||||
* isometric-display graphical MUD).
|
||||
*/
|
||||
public class TileManager
|
||||
{
|
||||
private static var log :Log = Log.getLog(TileManager);
|
||||
|
||||
/**
|
||||
* Sets the tileset repository that will be used by the tile manager when tiles are requested
|
||||
* by tileset id.
|
||||
*/
|
||||
public function setTileSetRepository (setrep :TileSetRepository) :void
|
||||
{
|
||||
_setrep = setrep;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the tileset repository currently in use.
|
||||
*/
|
||||
public function getTileSetRepository () :TileSetRepository
|
||||
{
|
||||
return _setrep;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the tileset with the specified id. Tilesets are fetched from the tileset repository
|
||||
* supplied via {@link #setTileSetRepository}, and are subsequently cached.
|
||||
*
|
||||
* @param tileSetId the unique identifier for the desired tileset.
|
||||
*
|
||||
* @exception NoSuchTileSetError thrown if no tileset exists with the specified id or if
|
||||
* an underlying error occurs with the tileset repository's persistence mechanism.
|
||||
*/
|
||||
public function getTileSet (tileSetId :int) :TileSet
|
||||
{
|
||||
// make sure we have a repository configured
|
||||
if (_setrep == null) {
|
||||
throw new NoSuchTileSetError(tileSetId);
|
||||
}
|
||||
|
||||
try {
|
||||
return _setrep.getTileSet(tileSetId);
|
||||
} catch (pe :Error) {
|
||||
log.warning("Failure loading tileset", "id", tileSetId, pe);
|
||||
throw new NoSuchTileSetError(tileSetId);
|
||||
}
|
||||
|
||||
// Unreachable.
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the tileset with the specified name.
|
||||
*
|
||||
* @throws NoSuchTileSetError if no tileset with the specified name is available via our
|
||||
* configured tile set repository.
|
||||
*/
|
||||
public function getTileSetByName (name :String) :TileSet
|
||||
{
|
||||
// make sure we have a repository configured
|
||||
if (_setrep == null) {
|
||||
throw new NoSuchTileSetError(name);
|
||||
}
|
||||
|
||||
try {
|
||||
return _setrep.getTileSetByName(name);
|
||||
} catch (pe :Error) {
|
||||
log.warning("Failure loading tileset", "name", name, "error", pe);
|
||||
throw new NoSuchTileSetError(name);
|
||||
}
|
||||
|
||||
// Unreachable.
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Tile} object with the specified fully qualified tile id. The supplied
|
||||
* colorizer will be used to recolor the tile.
|
||||
*
|
||||
* @see TileUtil#getFQTileId
|
||||
*/
|
||||
public function getTile (fqTileId :int, rizer :Colorizer = null) :Tile
|
||||
{
|
||||
return getTileBySet(TileUtil.getTileSetId(fqTileId),
|
||||
TileUtil.getTileIndex(fqTileId), rizer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Tile} object from the specified tileset at the specified index.
|
||||
*
|
||||
* @param tileSetId the tileset id.
|
||||
* @param tileIndex the index of the tile to be retrieved.
|
||||
*
|
||||
* @return the tile object.
|
||||
*/
|
||||
public function getTileBySet (tileSetId :int, tileIndex :int, rizer :Colorizer) :Tile
|
||||
{
|
||||
var set :TileSet = getTileSet(tileSetId);
|
||||
return set.getTile(tileIndex, rizer);
|
||||
}
|
||||
|
||||
public function ensureLoaded (tileSets :Set, completeCallback :Function,
|
||||
progressCallback :Function) :void
|
||||
{
|
||||
_setrep.ensureLoaded(tileSets, completeCallback, progressCallback);
|
||||
}
|
||||
|
||||
/** The tile set repository. */
|
||||
protected var _setrep :TileSetRepository;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,401 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.media.tile {
|
||||
|
||||
import flash.display.Bitmap;
|
||||
import flash.display.DisplayObject;
|
||||
import flash.geom.Rectangle;
|
||||
|
||||
import com.threerings.display.ImageUtil;
|
||||
import com.threerings.io.ObjectInputStream;
|
||||
import com.threerings.io.ObjectOutputStream;
|
||||
import com.threerings.io.Streamable;
|
||||
import com.threerings.media.image.Colorization;
|
||||
import com.threerings.util.Cloneable;
|
||||
import com.threerings.util.Hashable;
|
||||
import com.threerings.util.Log;
|
||||
import com.threerings.util.StringUtil;
|
||||
import com.threerings.util.Throttle;
|
||||
import com.threerings.util.maps.HashMap;
|
||||
import com.threerings.util.maps.WeakValueMap;
|
||||
|
||||
public /* abstract */ class TileSet
|
||||
implements Streamable, Hashable, Cloneable
|
||||
{
|
||||
private static var log :Log = Log.getLog(TileSet);
|
||||
|
||||
public function readObject (oin :ObjectInputStream) :void
|
||||
{
|
||||
_imagePath = oin.readUTF();
|
||||
_name = oin.readUTF();
|
||||
}
|
||||
|
||||
public function writeObject (oout :ObjectOutputStream) :void
|
||||
{
|
||||
oout.writeUTF(_imagePath);
|
||||
oout.writeUTF(_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures this tileset with an image provider that it can use to load its tileset image.
|
||||
* This will be called automatically when the tileset is fetched via the {@link TileManager}.
|
||||
*/
|
||||
public function setImageProvider (improv :ImageProvider) :void
|
||||
{
|
||||
_improv = improv;
|
||||
|
||||
if (_improv != null) {
|
||||
for each (var arr :Array in _pending) {
|
||||
initTile(arr[0], arr[1], arr[2]);
|
||||
}
|
||||
}
|
||||
_pending = [];
|
||||
|
||||
loaded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the tileset name.
|
||||
*/
|
||||
public function getName () :String
|
||||
{
|
||||
return (_name == null) ? _imagePath : _name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the tileset name.
|
||||
*/
|
||||
public function setName (name :String) :void
|
||||
{
|
||||
_name = name;
|
||||
}
|
||||
|
||||
public function hashCode () :int
|
||||
{
|
||||
return StringUtil.hashCode(getName());
|
||||
}
|
||||
|
||||
public function equals (other :Object) :Boolean
|
||||
{
|
||||
return other === this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the path to the image that will be used by this tileset. This must be called before the
|
||||
* first call to {@link #getTile}.
|
||||
*/
|
||||
public function setImagePath (imagePath :String) :void
|
||||
{
|
||||
_imagePath = imagePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to the composite image used by this tileset.
|
||||
*/
|
||||
public function getImagePath () :String
|
||||
{
|
||||
return _imagePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of tiles in the tileset.
|
||||
*/
|
||||
public function getTileCount () :int
|
||||
{
|
||||
throw new Error("abstract");
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes and fills in the bounds for the specified tile based on the mechanism used by the
|
||||
* derived class to do such things. The width and height of the bounds should be the size of
|
||||
* the tile image and the x and y offset should be the offset in the tileset image for the
|
||||
* image data of the specified tile.
|
||||
*
|
||||
* @param tileIndex the index of the tile whose bounds are to be computed.
|
||||
* @param bounds the rectangle object into which to fill the bounds.
|
||||
*
|
||||
* @return the rectangle passed into the bounds parameter.
|
||||
*/
|
||||
public function computeTileBounds (tileIndex :int) :Rectangle
|
||||
{
|
||||
throw new Error("abstract");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link Tile} object from this tileset corresponding to the specified tile id and
|
||||
* returns that tile. A null tile will never be returned, but one with an error image may be
|
||||
* returned if a problem occurs loading the underlying tileset image.
|
||||
*
|
||||
* @param tileIndex the index of the tile in the tileset. Tile indexes start with zero as the
|
||||
* upper left tile and increase by one as the tiles move left to right and top to bottom over
|
||||
* the source image.
|
||||
* @param zations colorizations to be applied to the tile image prior to returning it. These
|
||||
* may be null for uncolorized images.
|
||||
*
|
||||
* @return the tile object.
|
||||
*/
|
||||
public function getTile (tileIndex :int, zations :* = null) :Tile
|
||||
{
|
||||
// Default to using our default tileset colorizations.
|
||||
if (zations == null) {
|
||||
zations = _zations;
|
||||
} else if (zations is Colorizer) {
|
||||
zations = getTileColorizations(tileIndex, zations);
|
||||
}
|
||||
|
||||
var tile :Tile = null;
|
||||
|
||||
// first look in the active set; if it's in use by anyone or in the cache, it will be in
|
||||
// the active set
|
||||
var key :Tile_Key = new Tile_Key(this, tileIndex, zations);
|
||||
tile = _atiles.get(key);
|
||||
|
||||
// if it's not in the active set, it's not in memory; so load it
|
||||
if (tile == null) {
|
||||
tile = createTile();
|
||||
tile.key = key;
|
||||
initTile(tile, tileIndex, zations);
|
||||
_atiles.put(tile.key, tile);
|
||||
}
|
||||
|
||||
// periodically report our image cache performance
|
||||
reportCachePerformance();
|
||||
|
||||
return tile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a prepared version of the image that would be used by the tile at the specified
|
||||
* index. Because tilesets are often used simply to provide access to a collection of uniform
|
||||
* images, this method is provided to bypass the creation of a {@link Tile} object when all
|
||||
* that is desired is access to the underlying image.
|
||||
*/
|
||||
public function getTileImage (tileIndex :int, zations :Array, callback :Function)
|
||||
:void
|
||||
{
|
||||
// If they weren't specified, get the default zations for this tile.
|
||||
if (zations == null) {
|
||||
zations = getTileColorizations(tileIndex, null);
|
||||
}
|
||||
|
||||
var bounds :Rectangle = computeTileBounds(tileIndex);
|
||||
var image :DisplayObject = null;
|
||||
if (checkTileIndex(tileIndex)) {
|
||||
if (_improv == null) {
|
||||
log.warning("Aiya! Tile set missing image provider [path=" + _imagePath + "].");
|
||||
callback(new Bitmap(ImageUtil.createErrorBitmap()));
|
||||
|
||||
} else {
|
||||
_improv.getTileImage(_imagePath, bounds, zations,
|
||||
function(result :DisplayObject) :void {
|
||||
if (result == null) {
|
||||
result = new Bitmap(ImageUtil.createErrorBitmap());
|
||||
}
|
||||
callback(result);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
callback(ImageUtil.createErrorImage(bounds.width, bounds.height));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns colorizations for the specified tile image. The default is to return any
|
||||
* colorizations associated with the tileset via a call to {@link #clone(Colorization[])},
|
||||
* however derived classes may have dynamic colorization policies that look up colorization
|
||||
* assignments from the supplied colorizer.
|
||||
*/
|
||||
protected function getTileColorizations (tileIndex :int, rizer :Colorizer) :Array
|
||||
{
|
||||
return _zations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to ensure that the specified tile index is valid.
|
||||
*/
|
||||
protected function checkTileIndex (tileIndex :int) :Boolean
|
||||
{
|
||||
var tcount :int = getTileCount();
|
||||
if (tileIndex >= 0 && tileIndex < tcount) {
|
||||
return true;
|
||||
} else {
|
||||
log.warning("Requested invalid tile [tset=" + this + ", index=" + tileIndex + "].",
|
||||
new Error());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a blank tile of the appropriate type for this tileset.
|
||||
*
|
||||
* @return a blank tile ready to be populated with its image and metadata.
|
||||
*/
|
||||
protected function createTile () :Tile
|
||||
{
|
||||
return new Tile();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the supplied tile. Derived classes can override this method to add
|
||||
* in their own tile information, but should be sure to call <code>super.initTile()</code>.
|
||||
*
|
||||
* @param tile the tile to initialize.
|
||||
* @param tileIndex the index of the tile.
|
||||
* @param zations the colorizations to be used when generating the tile image.
|
||||
*/
|
||||
protected function initTile (tile :Tile, tileIndex :int, zations :Array) :void
|
||||
{
|
||||
if (_improv != null) {
|
||||
getTileImage(tileIndex, zations, function(result :DisplayObject) :void {
|
||||
tile.setImage(result);
|
||||
});
|
||||
} else {
|
||||
_pending.push([tile, tileIndex, zations]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports statistics detailing the image manager cache performance and the current size of the
|
||||
* cached images.
|
||||
*/
|
||||
protected function reportCachePerformance () :void
|
||||
{
|
||||
if (_improv == null ||
|
||||
_cacheStatThrottle.throttleOp()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// compute our estimated memory usage
|
||||
var amem :int = 0;
|
||||
var asize :int = 0;
|
||||
// first total up the active tiles
|
||||
for each (var tile :Tile in _atiles.values()) {
|
||||
if (tile != null) {
|
||||
asize++;
|
||||
}
|
||||
}
|
||||
log.info("Tile caches [seen=" + _atiles.size() + ", asize=" + asize + "].");
|
||||
}
|
||||
|
||||
public function toString () :String
|
||||
{
|
||||
return "[" + toStringBuf(new String()) + "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* Derived classes can override this, calling <code>super.toString(buf)</code> and then
|
||||
* appending additional information to the buffer.
|
||||
*/
|
||||
protected function toStringBuf (buf :String) :String
|
||||
{
|
||||
buf = buf.concat("name=", _name);
|
||||
buf = buf.concat(", path=", _imagePath);
|
||||
buf = buf.concat(", tileCount=", getTileCount());
|
||||
return buf;
|
||||
}
|
||||
|
||||
public function isLoaded () :Boolean
|
||||
{
|
||||
return _isLoaded;
|
||||
}
|
||||
|
||||
public function loaded () :void
|
||||
{
|
||||
_isLoaded = true;
|
||||
|
||||
// Notify them all and clear our list.
|
||||
for each (var func :Function in _notifyOnLoad) {
|
||||
func(this);
|
||||
}
|
||||
_notifyOnLoad = [];
|
||||
}
|
||||
|
||||
public function notifyOnLoad (func :Function) :void
|
||||
{
|
||||
if (isLoaded()) {
|
||||
func(this);
|
||||
} else {
|
||||
_notifyOnLoad.push(func);
|
||||
}
|
||||
}
|
||||
|
||||
protected function populateFromXml (xml :XML) :void
|
||||
{
|
||||
_name = xml.@name;
|
||||
_imagePath = xml.imagePath;
|
||||
}
|
||||
|
||||
public function clone () :Object
|
||||
{
|
||||
var newSet :TileSet = createClone();
|
||||
populateClone(newSet);
|
||||
return newSet;
|
||||
}
|
||||
|
||||
public function populateClone (clone :TileSet) :void
|
||||
{
|
||||
clone._isLoaded = _isLoaded;
|
||||
clone._imagePath = _imagePath;
|
||||
clone._name = _name;
|
||||
clone._improv = _improv;
|
||||
}
|
||||
|
||||
public function createClone () :TileSet
|
||||
{
|
||||
return new TileSet();
|
||||
}
|
||||
|
||||
public function cloneWithZations (zations :Array) :TileSet
|
||||
{
|
||||
var newSet :TileSet = TileSet(clone());
|
||||
newSet._zations = zations;
|
||||
return newSet;
|
||||
}
|
||||
|
||||
/** Whether all the media for this tileset is loaded and ready. */
|
||||
protected var _isLoaded :Boolean;
|
||||
|
||||
/** The path to the file containing the tile images. */
|
||||
protected var _imagePath :String;
|
||||
|
||||
/** The tileset name. */
|
||||
protected var _name :String;
|
||||
|
||||
/** Colorizations to be applied to tiles created from this tileset. */
|
||||
protected var _zations :Array; /* of */ Colorization;
|
||||
|
||||
/** The entity from which we obtain our tile image. */
|
||||
protected var _improv :ImageProvider;
|
||||
|
||||
/** Everyone who cares when we're loaded. */
|
||||
protected var _notifyOnLoad :Array = [];
|
||||
|
||||
/** Tiles awaiting an image provider. */
|
||||
protected var _pending :Array = [];
|
||||
|
||||
/** A map containing weak references to all "active" tiles. */
|
||||
protected static var _atiles :WeakValueMap = new WeakValueMap(new HashMap());
|
||||
|
||||
/** Throttle our cache status logging to once every 300 seconds. */
|
||||
protected static var _cacheStatThrottle :Throttle = new Throttle(1, 300000);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.media.tile {
|
||||
|
||||
public interface TileSetIdMap
|
||||
{
|
||||
/**
|
||||
* Returns the unique identifier for the named tileset.
|
||||
*/
|
||||
function getTileSetId (tileSetName :String) :int
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.media.tile {
|
||||
|
||||
import com.threerings.util.Set;
|
||||
|
||||
public interface TileSetRepository {
|
||||
|
||||
/**
|
||||
* Returns an iterator over the identifiers of all {@link TileSet}
|
||||
* objects available.
|
||||
*/
|
||||
function enumerateTileSetIds () :Array;
|
||||
|
||||
/**
|
||||
* Returns an iterator over all {@link TileSet} objects available.
|
||||
*/
|
||||
function enumerateTileSets () :Array;
|
||||
|
||||
/**
|
||||
* Returns the {@link TileSet} with the specified tile set
|
||||
* identifier. The repository is responsible for configuring the tile
|
||||
* set with an image provider.
|
||||
*
|
||||
* @exception NoSuchTileSetException thrown if no tileset exists with
|
||||
* the specified identifier.
|
||||
* @exception PersistenceException thrown if an error occurs
|
||||
* communicating with the underlying persistence mechanism.
|
||||
*/
|
||||
function getTileSet (tileSetId :int) :TileSet;
|
||||
|
||||
/**
|
||||
* Returns the unique identifier of the {@link TileSet} with the
|
||||
* specified tile set name.
|
||||
*
|
||||
* @exception NoSuchTileSetException thrown if no tileset exists with
|
||||
* the specified name.
|
||||
* @exception PersistenceException thrown if an error occurs
|
||||
* communicating with the underlying persistence mechanism.
|
||||
*/
|
||||
function getTileSetId (setName :String) :int;
|
||||
|
||||
/**
|
||||
* Returns the {@link TileSet} with the specified tile set name. The
|
||||
* repository is responsible for configuring the tile set with an
|
||||
* image provider.
|
||||
*
|
||||
* @exception NoSuchTileSetException thrown if no tileset exists with
|
||||
* the specified name.
|
||||
* @exception PersistenceException thrown if an error occurs
|
||||
* communicating with the underlying persistence mechanism.
|
||||
*/
|
||||
function getTileSetByName (setName :String) :TileSet;
|
||||
|
||||
/**
|
||||
* Ensures we are ready with quick-access to all the specified tilesets.
|
||||
*/
|
||||
function ensureLoaded (tileSets :Set, completeCallback :Function,
|
||||
progressCallback :Function) :void;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.media.tile {
|
||||
|
||||
public class TileUtil
|
||||
{
|
||||
/**
|
||||
* Generates a fully-qualified tile id given the supplied tileset id
|
||||
* and tile index.
|
||||
*/
|
||||
public static function getFQTileId (tileSetId :int, tileIndex :int) :int
|
||||
{
|
||||
return (tileSetId << 16) | tileIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the tile set id from the supplied fully qualified tile id.
|
||||
*/
|
||||
public static function getTileSetId (fqTileId :int) :int
|
||||
{
|
||||
return (fqTileId >> 16);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the tile index from the supplied fully qualified tile id.
|
||||
*/
|
||||
public static function getTileIndex (fqTileId :int) :int
|
||||
{
|
||||
return (fqTileId & 0xFFFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute some hash value for "randomizing" tileset picks
|
||||
* based on x and y coordinates.
|
||||
* NOTE: Because actionscript doesn't handle longs well, this does NOT match the implementation
|
||||
* of the java version of getTileHash()
|
||||
*
|
||||
* @return a positive, seemingly random number based on x and y.
|
||||
*/
|
||||
public static function getTileHash (x :int, y :int) :int
|
||||
{
|
||||
var seed :int = (((x << 2) ^ y) ^ MULTIPLIER) & MASK;
|
||||
var hash :int = (seed * MULTIPLIER + ADDEND) & MASK;
|
||||
return hash >>> 10;
|
||||
}
|
||||
|
||||
protected static const MULTIPLIER :int = 0x5E66D;
|
||||
protected static const ADDEND :int = 0xB;
|
||||
protected static const MASK :int = (1 << 16) - 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.media.tile {
|
||||
|
||||
import com.threerings.media.image.Colorization;
|
||||
import com.threerings.util.Arrays;
|
||||
import com.threerings.util.Hashable;
|
||||
|
||||
/** Used when caching tiles. */
|
||||
public class Tile_Key
|
||||
implements Hashable
|
||||
{
|
||||
public var tileSet :TileSet;
|
||||
public var tileIndex :int;
|
||||
public var zations :Array;
|
||||
|
||||
public function Tile_Key (tileSet :TileSet, tileIndex :int, zations :Array) {
|
||||
this.tileSet = tileSet;
|
||||
this.tileIndex = tileIndex;
|
||||
this.zations = zations;
|
||||
}
|
||||
|
||||
public function equals (other :Object) :Boolean
|
||||
{
|
||||
if (other is Tile_Key) {
|
||||
var okey :Tile_Key = Tile_Key(other);
|
||||
return (tileSet == okey.tileSet &&
|
||||
tileIndex == okey.tileIndex &&
|
||||
Arrays.equals(zations, okey.zations));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function hashCode () :int
|
||||
{
|
||||
var code :int = (tileSet == null) ? tileIndex :
|
||||
(tileSet.hashCode() ^ tileIndex);
|
||||
var zcount :int = (zations == null) ? 0 : zations.length;
|
||||
for each (var zation :Colorization in zations) {
|
||||
if (zation != null) {
|
||||
code ^= zation.hashCode();
|
||||
}
|
||||
}
|
||||
return code;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.media.tile.bundle {
|
||||
|
||||
import flash.display.DisplayObject;
|
||||
|
||||
import com.threerings.io.ObjectOutputStream;
|
||||
import com.threerings.io.ObjectInputStream;
|
||||
import com.threerings.io.Streamable;
|
||||
import com.threerings.util.DataPack;
|
||||
import com.threerings.util.maps.DictionaryMap;
|
||||
import com.threerings.media.tile.BaseTileSet;
|
||||
import com.threerings.media.tile.ObjectTileSet;
|
||||
import com.threerings.media.tile.SwissArmyTileSet;
|
||||
import com.threerings.media.tile.TileSet;
|
||||
import com.threerings.media.tile.TileSetIdMap;
|
||||
|
||||
public class TileSetBundle extends DictionaryMap
|
||||
{
|
||||
public function init (pack :DataPack) :void
|
||||
{
|
||||
_pack = pack;
|
||||
}
|
||||
|
||||
public static function fromXml (xml :XML, idMap :TileSetIdMap) :TileSetBundle
|
||||
{
|
||||
var bundle :TileSetBundle = new TileSetBundle();
|
||||
for each (var objXml :XML in xml.object.tileset) {
|
||||
var objId :int = idMap.getTileSetId(objXml.@name);
|
||||
bundle.put(objId, ObjectTileSet.fromXml(objXml));
|
||||
}
|
||||
for each (var baseXml :XML in xml.base.tileset) {
|
||||
var baseId :int = idMap.getTileSetId(baseXml.@name);
|
||||
bundle.put(baseId, BaseTileSet.fromXml(baseXml));
|
||||
}
|
||||
for each (var fringeXml :XML in xml.fringe.tileset) {
|
||||
var fringeId :int = idMap.getTileSetId(fringeXml.@name);
|
||||
bundle.put(fringeId, SwissArmyTileSet.fromXml(fringeXml));
|
||||
}
|
||||
return bundle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the image from our data pack.
|
||||
*/
|
||||
public function loadImage (path :String) :DisplayObject
|
||||
{
|
||||
return DisplayObject(_pack.getFile(path));
|
||||
}
|
||||
|
||||
/** The data pack from which we can grab images. */
|
||||
protected var _pack :DataPack;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.media.util {
|
||||
|
||||
import com.threerings.util.MathUtil;
|
||||
|
||||
/**
|
||||
* Searches for the shortest traversable path between locations.
|
||||
*/
|
||||
public class AStarPathSearch
|
||||
{
|
||||
/**
|
||||
* Creates a searching object using tpred to check for traversability and stepper to find
|
||||
* all the valid adjacent spots and the stepping costs & estimates.
|
||||
*/
|
||||
public function AStarPathSearch (tpred :TraversalPred,
|
||||
stepper :AStarPathSearch_Stepper = null)
|
||||
{
|
||||
_tpred = tpred;
|
||||
|
||||
if (stepper == null) {
|
||||
_stepper = new AStarPathSearch_Stepper();
|
||||
} else {
|
||||
_stepper = stepper;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of <code>Point</code> objects representing a path from coordinates
|
||||
* <code>(ax, by)</code> to <code>(bx, by)</code>, inclusive, determined by performing an
|
||||
* A* search in the given scene's base tile layer. Assumes the starting and destination nodes
|
||||
* are traversable by the specified traverser.
|
||||
*
|
||||
* @param trav the traverser to follow the path.
|
||||
* @param longest the longest allowable path in tile traversals. This arg must be small enough
|
||||
* that _stepper.getMaxCost(longest) < Integer.MAX_VALUE
|
||||
* @param ax the starting x-position in tile coordinates.
|
||||
* @param ay the starting y-position in tile coordinates.
|
||||
* @param bx the ending x-position in tile coordinates.
|
||||
* @param by the ending y-position in tile coordinates.
|
||||
* @param partial if true, a partial path will be returned that gets us as close as we can to
|
||||
* the goal in the event that a complete path cannot be located.
|
||||
*
|
||||
* @return the list of points in the path, or null if no path could be found.
|
||||
*/
|
||||
public function getPath (trav :Object, longest :int, ax :int, ay :int, bx :int, by :int,
|
||||
partial :Boolean) :Array
|
||||
{
|
||||
var info :AStarPathSearch_Info = new AStarPathSearch_Info(_tpred, trav,
|
||||
_stepper.getMaxCost(longest), bx, by);
|
||||
|
||||
// set up the starting node
|
||||
var s :AStarPathSearch_Node = info.getNode(ax, ay);
|
||||
s.g = 0;
|
||||
s.h = _stepper.getDistanceEstimate(ax, ay, bx, by);
|
||||
s.f = s.g + s.h;
|
||||
|
||||
// push starting node on the open list
|
||||
info.open.add(s);
|
||||
|
||||
// track the best path
|
||||
var bestdist :Number = Number.POSITIVE_INFINITY;
|
||||
var bestpath :AStarPathSearch_Node = null;
|
||||
|
||||
// while there are more nodes on the open list
|
||||
while (info.open.size() > 0) {
|
||||
// pop the best node so far from open
|
||||
var n :AStarPathSearch_Node = null;
|
||||
|
||||
info.open.forEach(function (val :AStarPathSearch_Node) :Boolean {
|
||||
n = val;
|
||||
return true;
|
||||
});
|
||||
info.open.remove(n);
|
||||
|
||||
// if node is a goal node
|
||||
if (n.x == bx && n.y == by) {
|
||||
// construct and return the acceptable path
|
||||
return n.getNodePath();
|
||||
|
||||
} else if (partial) {
|
||||
var pathdist :Number = MathUtil.distance(n.x, n.y, bx, by);
|
||||
if (pathdist < bestdist) {
|
||||
bestdist = pathdist;
|
||||
bestpath = n;
|
||||
}
|
||||
}
|
||||
|
||||
// consider each successor of the node
|
||||
_stepper.considerSteps(info, n, n.x, n.y);
|
||||
|
||||
// push the node on the closed list
|
||||
info.closed.add(n);
|
||||
}
|
||||
|
||||
// return the best path we could find if we were asked to do so
|
||||
if (bestpath != null) {
|
||||
return bestpath.getNodePath();
|
||||
}
|
||||
|
||||
// no path found
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** In charge of determining if we can walk across various bits. */
|
||||
protected var _tpred :TraversalPred;
|
||||
|
||||
/** In charge of finding all the steps from a given spot. */
|
||||
protected var _stepper :AStarPathSearch_Stepper;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.media.util {
|
||||
|
||||
import com.threerings.util.Maps;
|
||||
|
||||
import com.threerings.util.Map;
|
||||
import com.threerings.util.Set;
|
||||
import com.threerings.util.Sets;
|
||||
|
||||
/**
|
||||
* A holding class to contain the wealth of information referenced
|
||||
* while performing an A* search for a path through a tile array.
|
||||
*/
|
||||
public class AStarPathSearch_Info
|
||||
{
|
||||
/** Knows whether or not tiles are traversable. */
|
||||
public var tpred :TraversalPred;
|
||||
|
||||
/** The tile array dimensions. */
|
||||
public var tilewid :int;
|
||||
public var tilehei :int;
|
||||
|
||||
/** The traverser moving along the path. */
|
||||
public var trav :Object;
|
||||
|
||||
/** The set of open nodes being searched. */
|
||||
// TODO - This would benefit from a more efficient implementation of SortedSet, but for now
|
||||
// it is good enough.
|
||||
public var open :Set;
|
||||
|
||||
/** The set of closed nodes being searched. */
|
||||
public var closed :Set;
|
||||
|
||||
/** The destination coordinates in the tile array. */
|
||||
public var destx :int;
|
||||
public var desty :int;
|
||||
|
||||
/** The maximum cost of any path that we'll consider. */
|
||||
public var maxcost :int;
|
||||
|
||||
public function AStarPathSearch_Info (tpred :TraversalPred, trav :Object, maxcost :int,
|
||||
destx :int, desty :int)
|
||||
{
|
||||
// save off references
|
||||
this.tpred = tpred;
|
||||
this.trav = trav;
|
||||
this.destx = destx;
|
||||
this.desty = desty;
|
||||
|
||||
// compute our maximum path cost
|
||||
this.maxcost = maxcost;
|
||||
|
||||
// construct the open and closed lists
|
||||
open = Sets.newSortedSetOf(AStarPathSearch_Node);
|
||||
closed = Sets.newSetOf(AStarPathSearch_Node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether moving from the given source to destination coordinates is a valid
|
||||
* move.
|
||||
*/
|
||||
protected function isStepValid (sx :int, sy :int, dx :int, dy :int) :Boolean
|
||||
{
|
||||
// not traversable if the destination itself fails test
|
||||
if (tpred is ExtendedTraversalPred) {
|
||||
if (!ExtendedTraversalPred(tpred).canTraverseBetween(trav, sx, sy, dx, dy)) {
|
||||
return false;
|
||||
}
|
||||
} else if (!isTraversable(dx, dy)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if the step is diagonal, make sure the corners don't impede our progress
|
||||
if ((Math.abs(dx - sx) == 1) && (Math.abs(dy - sy) == 1)) {
|
||||
return isTraversable(dx, sy) && isTraversable(sx, dy);
|
||||
}
|
||||
|
||||
// non-diagonals are always traversable
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given coordinate is valid and traversable.
|
||||
*/
|
||||
protected function isTraversable (x :int, y :int) :Boolean
|
||||
{
|
||||
return tpred.canTraverse(trav, x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create the node for the specified point.
|
||||
*/
|
||||
public function getNode (x :int, y :int) :AStarPathSearch_Node
|
||||
{
|
||||
// note: this _could_ break for unusual values of x and y.
|
||||
// perhaps use a IntTuple as a key? Bleah.
|
||||
var key :int = (x << 16) | (y & 0xffff);
|
||||
var node :AStarPathSearch_Node = _nodes.get(key);
|
||||
if (node == null) {
|
||||
node = new AStarPathSearch_Node(x, y);
|
||||
_nodes.put(key, node);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Consider the step <code>(n.x, n.y)</code> to <code>(x, y)</code> for possible inclusion
|
||||
* in the path.
|
||||
*
|
||||
* @param info the info object.
|
||||
* @param node the originating node for the step.
|
||||
* @param x the x-coordinate for the destination step.
|
||||
* @param y the y-coordinate for the destination step.
|
||||
*/
|
||||
public function considerStep (node :AStarPathSearch_Node, x :int, y :int, cost :int,
|
||||
stepper :AStarPathSearch_Stepper) :void
|
||||
{
|
||||
// skip node if it's outside the map bounds or otherwise impassable
|
||||
if (!isStepValid(node.x, node.y, x, y)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// calculate the new cost for this node
|
||||
var newg :int = node.g + cost;
|
||||
|
||||
// make sure the cost is reasonable
|
||||
if (newg > maxcost) {
|
||||
return;
|
||||
}
|
||||
|
||||
// retrieve the node corresponding to this location
|
||||
var np :AStarPathSearch_Node = getNode(x, y);
|
||||
|
||||
// skip if it's already in the open or closed list or if its
|
||||
// actual cost is less than the just-calculated cost
|
||||
if ((open.contains(np) || closed.contains(np)) && np.g <= newg) {
|
||||
return;
|
||||
}
|
||||
|
||||
// remove the node from the open list since we're about to
|
||||
// modify its score which determines its placement in the list
|
||||
open.remove(np);
|
||||
|
||||
// update the node's information
|
||||
np.parent = node;
|
||||
np.g = newg;
|
||||
np.h = stepper.getDistanceEstimate(np.x, np.y, destx, desty);
|
||||
np.f = np.g + np.h;
|
||||
|
||||
// remove it from the closed list if it's present
|
||||
closed.remove(np);
|
||||
|
||||
// add it to the open list for further consideration
|
||||
open.add(np);
|
||||
}
|
||||
|
||||
/** The nodes being considered in the path. */
|
||||
protected var _nodes :Map = Maps.newMapOf(int);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.media.util {
|
||||
|
||||
import flash.geom.Point;
|
||||
|
||||
import com.threerings.util.Comparable;
|
||||
|
||||
public class AStarPathSearch_Node
|
||||
implements Comparable
|
||||
{
|
||||
/** The node coordinates. */
|
||||
public var x :int;
|
||||
public var y :int;
|
||||
|
||||
/** The actual cheapest cost of arriving here from the start. */
|
||||
public var g :int;
|
||||
|
||||
/** The heuristic estimate of the cost to the goal from here. */
|
||||
public var h :int;
|
||||
|
||||
/** The score assigned to this node. */
|
||||
public var f :int;
|
||||
|
||||
/** The node from which we reached this node. */
|
||||
public var parent :AStarPathSearch_Node;
|
||||
|
||||
/** The node's monotonically-increasing unique identifier. */
|
||||
public var id :int;
|
||||
|
||||
public function AStarPathSearch_Node (x :int, y :int)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
id = _nextid++;
|
||||
}
|
||||
|
||||
public function compareTo (o :Object) :int
|
||||
{
|
||||
var n :AStarPathSearch_Node = AStarPathSearch_Node(o);
|
||||
var bf :int = n.f;
|
||||
|
||||
// since the set contract is fulfilled using the equality results returned here, and
|
||||
// we'd like to allow multiple nodes with equivalent scores in our set, we explicitly
|
||||
// define object equivalence as the result of object.equals(), else we use the unique
|
||||
// node id since it will return a consistent ordering for the objects.
|
||||
if (f == bf) {
|
||||
return (this == n) ? 0 : (id - n.id);
|
||||
}
|
||||
|
||||
return f - bf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of <code>Point</code> objects detailing the path from the first node (the
|
||||
* given node's ultimate parent) to the ending node (the given node itself.)
|
||||
*
|
||||
* @param node the ending node in the path.
|
||||
*
|
||||
* @return the list detailing the path.
|
||||
*/
|
||||
public function getNodePath () :Array
|
||||
{
|
||||
var cur :AStarPathSearch_Node = this;
|
||||
var path :Array = [];
|
||||
|
||||
while (cur != null) {
|
||||
// add to the head of the list since we're traversing from
|
||||
// the end to the beginning
|
||||
path.unshift(new Point(cur.x, cur.y));
|
||||
|
||||
// advance to the next node in the path
|
||||
cur = cur.parent;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/** The next unique node id. */
|
||||
protected static var _nextid :int = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.media.util {
|
||||
|
||||
/**
|
||||
* Considers all the possible steps the piece in question can take.
|
||||
*/
|
||||
public class AStarPathSearch_Stepper
|
||||
{
|
||||
public function AStarPathSearch_Stepper (considerDiagonals :Boolean = true)
|
||||
{
|
||||
_considerDiagonals = considerDiagonals;
|
||||
}
|
||||
|
||||
public function getMaxCost (longest :int) :int
|
||||
{
|
||||
return longest * ADJACENT_COST;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a heuristic estimate of the cost to get from <code>(ax, ay)</code> to
|
||||
* <code>(bx, by)</code>.
|
||||
*/
|
||||
public function getDistanceEstimate (ax :int, ay :int, bx :int, by :int) :int
|
||||
{
|
||||
// we're doing all of our cost calculations based on geometric distance times ten
|
||||
var dx :int = bx - ax;
|
||||
var dy :int = by - ay;
|
||||
return int(Math.floor(ADJACENT_COST * Math.sqrt(dx * dx + dy * dy)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Should call {@link #considerStep} in turn on all possible steps from the specified
|
||||
* coordinates. No checking must be done as to whether the step is legal, that will be
|
||||
* handled later. Just enumerate all possible steps.
|
||||
*/
|
||||
public function considerSteps (info :AStarPathSearch_Info, node :AStarPathSearch_Node,
|
||||
x :int, y :int) :void
|
||||
{
|
||||
info.considerStep(node, x, y - 1, ADJACENT_COST, this);
|
||||
info.considerStep(node, x, y + 1, ADJACENT_COST, this);
|
||||
info.considerStep(node, x - 1, y, ADJACENT_COST, this);
|
||||
info.considerStep(node, x + 1, y, ADJACENT_COST, this);
|
||||
if (_considerDiagonals) {
|
||||
info.considerStep(node, x - 1, y - 1, DIAGONAL_COST, this);
|
||||
info.considerStep(node, x + 1, y - 1, DIAGONAL_COST, this);
|
||||
info.considerStep(node, x - 1, y + 1, DIAGONAL_COST, this);
|
||||
info.considerStep(node, x + 1, y + 1, DIAGONAL_COST, this);
|
||||
}
|
||||
}
|
||||
|
||||
protected var _considerDiagonals :Boolean;
|
||||
|
||||
/** The standard cost to move between nodes. */
|
||||
protected static const ADJACENT_COST :int = 10;
|
||||
|
||||
/** The cost to move diagonally. */
|
||||
protected static const DIAGONAL_COST :int = int(Math.sqrt((ADJACENT_COST * ADJACENT_COST) * 2));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.media.util {
|
||||
|
||||
/**
|
||||
* Provides extended traversibility information when computing paths.
|
||||
*/
|
||||
public interface ExtendedTraversalPred extends TraversalPred
|
||||
{
|
||||
/**
|
||||
* Requests to know if the specific traverser (which was provided in the call to
|
||||
* {@link #getPath(TraversalPred,Object,int,int,int,int,int,boolean)}) can traverse from
|
||||
* the specified source tile coordinate to the specified destination tile coordinate.
|
||||
*/
|
||||
function canTraverseBetween (traverser :Object, sx :int, sy :int, dx :int, dy :int) :Boolean;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,376 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.media.util {
|
||||
|
||||
import flash.geom.Point;
|
||||
import flash.geom.Rectangle;
|
||||
|
||||
import com.threerings.util.ArrayIterator;
|
||||
import com.threerings.util.DirectionCodes;
|
||||
import com.threerings.util.DirectionUtil;
|
||||
import com.threerings.util.Iterator;
|
||||
import com.threerings.util.Log;
|
||||
import com.threerings.util.MathUtil;
|
||||
import com.threerings.util.StringUtil;
|
||||
|
||||
/**
|
||||
* The line segment path is used to cause a pathable to follow a path that
|
||||
* is made up of a sequence of line segments. There must be at least two
|
||||
* nodes in any worthwhile path. The direction of the first node in the
|
||||
* path is meaningless since the pathable begins at that node and will
|
||||
* therefore never be heading towards it.
|
||||
*/
|
||||
public class LineSegmentPath
|
||||
implements Path
|
||||
{
|
||||
private var log :Log = Log.getLog(LineSegmentPath);
|
||||
|
||||
/**
|
||||
* Constructs an empty line segment path.
|
||||
*/
|
||||
public function LineSegmentPath ()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a line segment path that consists of a single segment
|
||||
* connecting the point <code>(x1, y1)</code> with <code>(x2,
|
||||
* y2)</code>. The orientation for the first node is set arbitrarily
|
||||
* and the second node is oriented based on the vector between the two
|
||||
* nodes (in top-down coordinates).
|
||||
*/
|
||||
public static function createWithInts (x1 :int, y1 :int, x2 :int, y2 :int) :LineSegmentPath
|
||||
{
|
||||
var path :LineSegmentPath = new LineSegmentPath();
|
||||
path.addNode(x1, y1, DirectionCodes.NORTH);
|
||||
var p1 :Point = new Point(x1, y1);
|
||||
var p2 :Point = new Point(x2, y2);
|
||||
var dir :int = DirectionUtil.getDirectionForPts(p1, p2);
|
||||
path.addNode(x2, y2, dir);
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a line segment path between the two nodes with the
|
||||
* specified direction.
|
||||
*/
|
||||
public static function createWithPoints (p1 :Point, p2 :Point, dir :int) :LineSegmentPath
|
||||
{
|
||||
var path :LineSegmentPath = new LineSegmentPath();
|
||||
path.addNode(p1.x, p1.y, DirectionCodes.NORTH);
|
||||
path.addNode(p2.x, p2.y, dir);
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a line segment path with the specified list of points.
|
||||
* An arbitrary direction will be assigned to the starting node.
|
||||
*/
|
||||
public static function createWithList (points :Array) :LineSegmentPath
|
||||
{
|
||||
var path :LineSegmentPath = new LineSegmentPath();
|
||||
path.createPath(points);
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the orientation the sprite will face at the end of the
|
||||
* path.
|
||||
*/
|
||||
public function getFinalOrientation () :int
|
||||
{
|
||||
return (_nodes.length == 0) ? DirectionCodes.NORTH : _nodes[_nodes.length-1].dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a node to the path with the specified destination point and
|
||||
* facing direction.
|
||||
*
|
||||
* @param x the x-position.
|
||||
* @param y the y-position.
|
||||
* @param dir the facing direction.
|
||||
*/
|
||||
public function addNode (x :Number, y :Number, dir :int) :void
|
||||
{
|
||||
_nodes.push(new PathNode(x, y, dir));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the requested node index in the path, or null if no such
|
||||
* index exists.
|
||||
*
|
||||
* @param idx the node index.
|
||||
*
|
||||
* @return the path node.
|
||||
*/
|
||||
public function getNode (idx :int) :PathNode
|
||||
{
|
||||
return _nodes[idx];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of nodes in the path.
|
||||
*/
|
||||
public function size () :int
|
||||
{
|
||||
return _nodes.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the velocity of this pathable in pixels per millisecond. The
|
||||
* velocity is measured as pixels traversed along the path that the
|
||||
* pathable is traveling rather than in the x or y directions
|
||||
* individually. Note that the pathable velocity should not be
|
||||
* changed while a path is being traversed; doing so may result in the
|
||||
* pathable position changing unexpectedly.
|
||||
*
|
||||
* @param velocity the pathable velocity in pixels per millisecond.
|
||||
*/
|
||||
public function setVelocity (velocity :Number) :void
|
||||
{
|
||||
_vel = velocity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the velocity at which the pathable will need to travel
|
||||
* along this path such that it will arrive at the destination in
|
||||
* approximately the specified number of milliseconds. Efforts are
|
||||
* taken to get the pathable there as close to the desired time as
|
||||
* possible, but framerate variation may prevent it from arriving
|
||||
* exactly on time.
|
||||
*/
|
||||
public function setDuration (millis :int) :void
|
||||
{
|
||||
// if we have only zero or one nodes, we don't have enough
|
||||
// information to compute our velocity
|
||||
var ncount :int = _nodes.length;
|
||||
if (ncount < 2) {
|
||||
log.warning("Requested to set duration of bogus path " +
|
||||
"[path=" + this + ", duration=" + millis + "].");
|
||||
return;
|
||||
}
|
||||
|
||||
// compute the total distance along our path
|
||||
var distance :Number = 0;
|
||||
var start :PathNode = _nodes[0];
|
||||
for (var ii :int = 1; ii < ncount; ii++) {
|
||||
var end :PathNode = _nodes[ii];
|
||||
distance += MathUtil.distance(start.loc.x, start.loc.y, end.loc.x, end.loc.y);
|
||||
start = end;
|
||||
}
|
||||
|
||||
// set the velocity accordingly
|
||||
setVelocity(distance/millis);
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public function init (pable :Pathable, timestamp :int) :void
|
||||
{
|
||||
// give the pathable a chance to perform any starting antics
|
||||
pable.pathBeginning();
|
||||
|
||||
// if we have only one node then let the pathable know that we're
|
||||
// done straight away
|
||||
if (size() < 2) {
|
||||
// move the pathable to the location specified by the first
|
||||
// node (assuming we have a first node)
|
||||
if (size() == 1) {
|
||||
var node :PathNode = _nodes[0];
|
||||
pable.setLocation(node.loc.x, node.loc.y);
|
||||
}
|
||||
// and let the pathable know that we're done
|
||||
pable.pathCompleted(timestamp);
|
||||
return;
|
||||
}
|
||||
|
||||
// and an enumeration of the path nodes
|
||||
_niter = new ArrayIterator(_nodes);
|
||||
|
||||
// pretend like we were previously heading to our starting position
|
||||
_dest = getNextNode();
|
||||
|
||||
// begin traversing the path
|
||||
headToNextNode(pable, timestamp, timestamp);
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public function tick (pable :Pathable, timestamp :int) :Boolean
|
||||
{
|
||||
// figure out how far along this segment we should be
|
||||
var msecs :int = timestamp - _nodestamp;
|
||||
var travpix :Number = msecs * _vel;
|
||||
var pctdone :Number = travpix / _seglength;
|
||||
|
||||
// if we've moved beyond the end of the path, we need to adjust
|
||||
// the timestamp to determine how much time we used getting to the
|
||||
// end of this node, then move to the next one
|
||||
if (pctdone >= 1.0) {
|
||||
var used :int = int(_seglength / _vel);
|
||||
return headToNextNode(pable, _nodestamp + used, timestamp);
|
||||
}
|
||||
|
||||
// otherwise we position the pathable along the path
|
||||
var ox :Number = pable.getX();
|
||||
var oy :Number = pable.getY();
|
||||
var nx :Number = _src.loc.x + (_dest.loc.x - _src.loc.x) * pctdone;
|
||||
var ny :Number = _src.loc.y + (_dest.loc.y - _src.loc.y) * pctdone;
|
||||
|
||||
// Log.info("Moving pathable [msecs=" + msecs + ", pctdone=" + pctdone +
|
||||
// ", travpix=" + travpix + ", seglength=" + _seglength +
|
||||
// ", dx=" + (nx-ox) + ", dy=" + (ny-oy) + "].");
|
||||
|
||||
// only update the pathable's location if it actually moved
|
||||
if (ox != nx || oy != ny) {
|
||||
pable.setLocation(nx, ny);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public function fastForward (timeDelta :int) :void
|
||||
{
|
||||
_nodestamp += timeDelta;
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public function wasRemoved (pable :Pathable) :void
|
||||
{
|
||||
// nothing doing
|
||||
}
|
||||
|
||||
/**
|
||||
* Place the pathable moving along the path at the end of the previous
|
||||
* path node, face it appropriately for the next node, and start it on
|
||||
* its way. Returns whether the pathable position moved.
|
||||
*/
|
||||
protected function headToNextNode (pable :Pathable, startstamp :int, now :int) :Boolean
|
||||
{
|
||||
if (_niter == null) {
|
||||
throw new Error("headToNextNode() called before init()");
|
||||
}
|
||||
|
||||
// check to see if we've completed our path
|
||||
if (!_niter.hasNext()) {
|
||||
// move the pathable to the location of our last destination
|
||||
pable.setLocation(_dest.loc.x, _dest.loc.y);
|
||||
pable.pathCompleted(now);
|
||||
return true;
|
||||
}
|
||||
|
||||
// our previous destination is now our source
|
||||
_src = _dest;
|
||||
|
||||
// pop the next node off the path
|
||||
_dest = getNextNode();
|
||||
|
||||
// adjust the pathable's orientation
|
||||
if (_dest.dir != DirectionCodes.NONE) {
|
||||
pable.setOrientation(_dest.dir);
|
||||
}
|
||||
|
||||
// make a note of when we started traversing this node
|
||||
_nodestamp = startstamp;
|
||||
|
||||
// figure out the distance from source to destination
|
||||
_seglength = MathUtil.distance(_src.loc.x, _src.loc.y, _dest.loc.x, _dest.loc.y);
|
||||
|
||||
// if we're already there (the segment length is zero), we skip to
|
||||
// the next segment
|
||||
if (_seglength == 0) {
|
||||
return headToNextNode(pable, startstamp, now);
|
||||
}
|
||||
|
||||
// now update the pathable's position based on our progress thus far
|
||||
return tick(pable, now);
|
||||
}
|
||||
|
||||
public function toString () :String
|
||||
{
|
||||
return StringUtil.toString(_nodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the path with the path nodes that lead the pathable from
|
||||
* its starting position to the given destination coordinates
|
||||
* following the given list of screen coordinates.
|
||||
*/
|
||||
protected function createPath (points :Array) :void
|
||||
{
|
||||
var last :Point = null;
|
||||
var size :int = points.length;
|
||||
for (var ii :int = 0; ii < size; ii++) {
|
||||
var p :Point = points[ii];
|
||||
|
||||
var dir :int = (ii == 0) ? DirectionCodes.NORTH :
|
||||
DirectionUtil.getDirectionForPts(last, p);
|
||||
addNode(p.x, p.y, dir);
|
||||
last = p;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next node in the path.
|
||||
*/
|
||||
protected function getNextNode () :PathNode
|
||||
{
|
||||
return PathNode(_niter.next());
|
||||
}
|
||||
|
||||
/** The nodes that make up the path. */
|
||||
protected var _nodes :Array = [];
|
||||
|
||||
/** We use this when moving along this path. */
|
||||
protected var _niter :Iterator;
|
||||
|
||||
/** When moving, the pathable's source path node. */
|
||||
protected var _src :PathNode;
|
||||
|
||||
/** When moving, the pathable's destination path node. */
|
||||
protected var _dest :PathNode;
|
||||
|
||||
/** The time at which we started traversing the current node. */
|
||||
protected var _nodestamp :int;
|
||||
|
||||
/** The length in pixels of the current path segment. */
|
||||
protected var _seglength :Number;
|
||||
|
||||
/** The path velocity in pixels per millisecond. */
|
||||
protected var _vel :Number = DEFAULT_VELOCITY;
|
||||
|
||||
/** When moving, the pathable position including fractional pixels. */
|
||||
protected var _movex :Number;
|
||||
protected var _movey :Number;
|
||||
|
||||
/** When moving, the distance to move on each axis per tick. */
|
||||
protected var _incx :Number;
|
||||
protected var _incy :Number;
|
||||
|
||||
/** The distance to move on the straight path line per tick. */
|
||||
protected var _fracx :Number;
|
||||
protected var _fracy :Number;
|
||||
|
||||
/** Default pathable velocity. */
|
||||
protected static const DEFAULT_VELOCITY :Number= 0.2;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.media.util {
|
||||
|
||||
/**
|
||||
* A path is used to cause a {@link Pathable} to follow a particular path along the screen. The
|
||||
* {@link Pathable} is responsible for calling {@link #tick} on the path with reasonable frequency
|
||||
* (generally as a part of the frame tick. The path is responsible for updating the position of
|
||||
* the {@link Pathable} based on the time that has elapsed since the {@link Pathable} started down
|
||||
* the path.
|
||||
*
|
||||
* <p> The path should call the appropriate callbacks on the {@link Pathable} when appropriate
|
||||
* (e.g. {@link Pathable#pathBeginning}, {@link Pathable#pathCompleted}).
|
||||
*/
|
||||
public interface Path
|
||||
{
|
||||
/**
|
||||
* Called once to let the path prepare itself for the process of animating the supplied
|
||||
* pathable. Path users should also call {@link #tick} after {@link #init} with the same
|
||||
* initialization timestamp.
|
||||
*/
|
||||
function init (pable :Pathable, tickStamp :int) :void;
|
||||
|
||||
/**
|
||||
* Called to request that this path update the position of the specified pathable based on the
|
||||
* supplied timestamp information. A path should record its initial timestamp and determine
|
||||
* the progress of the pathable along the path based on the time elapsed since the pathable
|
||||
* began down the path.
|
||||
*
|
||||
* @param pable the pathable whose position should be updated.
|
||||
* @param tickStamp the timestamp associated with this frame.
|
||||
*
|
||||
* @return true if the pathable's position was updated, false if the path determined that the
|
||||
* pathable should not move at this time.
|
||||
*/
|
||||
function tick (pable :Pathable, tickStamp :int) :Boolean;
|
||||
|
||||
/**
|
||||
* This is called if the pathable is paused for some length of time and then unpaused. Paths
|
||||
* should adjust any time stamps they are maintaining internally by the delta so that time
|
||||
* maintains the illusion of flowing smoothly forward.
|
||||
*/
|
||||
function fastForward (timeDelta :int) :void;
|
||||
|
||||
/**
|
||||
* When a path is removed from a pathable, whether that is because the path was completed or
|
||||
* because it was replaced by another path, this method will be called to let the path know
|
||||
* that it is no longer associated with this pathable.
|
||||
*/
|
||||
function wasRemoved (pable :Pathable) :void;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.media.util {
|
||||
|
||||
import flash.geom.Point;
|
||||
|
||||
/**
|
||||
* A path node is a single destination point in a {@link Path}.
|
||||
*/
|
||||
public class PathNode
|
||||
{
|
||||
/** The node coordinates in screen pixels. */
|
||||
public var loc :Point;
|
||||
|
||||
/** The direction to face while heading toward the node. */
|
||||
public var dir :int;
|
||||
|
||||
/**
|
||||
* Construct a path node object.
|
||||
*
|
||||
* @param x the node x-position.
|
||||
* @param y the node y-position.
|
||||
* @param dir the facing direction.
|
||||
*/
|
||||
public function PathNode (x :Number, y :Number, dir :int)
|
||||
{
|
||||
loc = new Point(x, y);
|
||||
this.dir = dir;
|
||||
}
|
||||
|
||||
public function toString () :String
|
||||
{
|
||||
return "[x=" + loc.x + ", y=" + loc.y + ", dir=" + dir + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.media.util {
|
||||
|
||||
import flash.geom.Rectangle;
|
||||
|
||||
/**
|
||||
* Used in conjunction with a {@link Path}.
|
||||
*/
|
||||
public interface Pathable
|
||||
{
|
||||
/**
|
||||
* Returns the pathable's current x coordinate.
|
||||
*/
|
||||
function getX () :Number;
|
||||
|
||||
/**
|
||||
* Returns the pathable's current y coordinate.
|
||||
*/
|
||||
function getY () :Number;
|
||||
|
||||
/**
|
||||
* Updates the pathable's current coordinates.
|
||||
*/
|
||||
function setLocation (x :Number, y :Number) :void;
|
||||
|
||||
/**
|
||||
* Will be called by a path when it moves the pathable in the
|
||||
* specified direction. Pathables that wish to face in the direction
|
||||
* they are moving can take advantage of this callback.
|
||||
*
|
||||
* @see DirectionCodes
|
||||
*/
|
||||
function setOrientation (orient :int) :void;
|
||||
|
||||
/**
|
||||
* Should return the orientation of the pathable, or {@link
|
||||
* DirectionCodes#NONE} if the pathable does not support orientation.
|
||||
*/
|
||||
function getOrientation () :int;
|
||||
|
||||
/**
|
||||
* Called by a path when this pathable is made to start along a path.
|
||||
*/
|
||||
function pathBeginning () :void;
|
||||
|
||||
/**
|
||||
* Called by a path when this pathable finishes moving along its path.
|
||||
*/
|
||||
function pathCompleted (timestamp :int) :void;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.media.util {
|
||||
|
||||
/**
|
||||
* Provides traversibility information when computing paths.
|
||||
*/
|
||||
public interface TraversalPred
|
||||
{
|
||||
/**
|
||||
* Requests to know if the specified traverser (which was provided in the call to
|
||||
* {@link #getPath(TraversalPred,Object,int,int,int,int,int,boolean)}) can traverse the
|
||||
* specified tile coordinate.
|
||||
*/
|
||||
function canTraverse (traverser :Object, x :int, y :int) :Boolean;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.miso.client {
|
||||
|
||||
import flash.display.DisplayObject;
|
||||
|
||||
import com.threerings.media.tile.Tile;
|
||||
import com.threerings.miso.util.MisoSceneMetrics;
|
||||
|
||||
public class BaseTileIsoSprite extends TileIsoSprite
|
||||
{
|
||||
public function BaseTileIsoSprite (x :int, y :int, tileId :int, tile :Tile,
|
||||
metrics :MisoSceneMetrics, fringe :Tile)
|
||||
{
|
||||
super(x, y, tileId, tile, 0, metrics, fringe);
|
||||
}
|
||||
|
||||
public override function layout (x :int, y :int, tile :Tile) :void
|
||||
{
|
||||
super.layout(x, y, tile);
|
||||
|
||||
setSize(1, 1, VERT_OFFSET);
|
||||
}
|
||||
|
||||
protected static const VERT_OFFSET :Number = 0.01;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.miso.client {
|
||||
|
||||
import as3isolib.geom.Pt;
|
||||
import as3isolib.geom.transformations.IAxonometricTransformation;
|
||||
|
||||
import com.threerings.miso.util.MisoSceneMetrics;
|
||||
|
||||
public class MisoMetricsTransformation implements IAxonometricTransformation
|
||||
{
|
||||
public function MisoMetricsTransformation (metrics :MisoSceneMetrics, zFact :Number)
|
||||
{
|
||||
_metrics = metrics;
|
||||
_zFact = zFact;
|
||||
}
|
||||
|
||||
public function screenToSpace (screenPt :Pt) :Pt
|
||||
{
|
||||
var tpos :Pt = new Pt();
|
||||
|
||||
// determine the upper-left of the quadrant that contains our point
|
||||
var zx :int = int(Math.floor(screenPt.x / _metrics.tilewid));
|
||||
var zy :int = int(Math.floor(screenPt.y / _metrics.tilehei));
|
||||
|
||||
// these are the screen coordinates of the tile's top
|
||||
var ox :int = (zx * _metrics.tilewid);
|
||||
var oy :int = (zy * _metrics.tilehei);
|
||||
|
||||
// these are the tile coordinates
|
||||
tpos.x = zy + zx;
|
||||
tpos.y = zy - zx;
|
||||
tpos.z = 0;
|
||||
|
||||
// these are the tile coordinates
|
||||
tpos.x = zy + zx; tpos.y = zy - zx;
|
||||
|
||||
// now determine which of the four tiles our point occupies
|
||||
var dx :int = screenPt.x - ox;
|
||||
var dy :int = screenPt.y - oy;
|
||||
|
||||
if (Math.round(_metrics.slopeY * dx + _metrics.tilehei) <= dy) {
|
||||
tpos.x += 1;
|
||||
}
|
||||
|
||||
if (Math.round(_metrics.slopeX * dx) > dy) {
|
||||
tpos.y -= 1;
|
||||
}
|
||||
|
||||
return tpos;
|
||||
}
|
||||
|
||||
public function spaceToScreen (spacePt :Pt) :Pt
|
||||
{
|
||||
var spos :Pt = new Pt();
|
||||
spos.x = (spacePt.x - spacePt.y) * _metrics.tilehwid;
|
||||
spos.y = (spacePt.x + spacePt.y) * _metrics.tilehhei - spacePt.z * _zFact;
|
||||
spos.z = 0;
|
||||
return spos;
|
||||
}
|
||||
|
||||
protected var _metrics :MisoSceneMetrics;
|
||||
|
||||
/** Handles any vertical transformations. */
|
||||
protected var _zFact :Number;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,732 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.miso.client {
|
||||
|
||||
import flash.utils.getTimer;
|
||||
|
||||
import flash.display.DisplayObject;
|
||||
import flash.display.Sprite;
|
||||
|
||||
import flash.events.Event;
|
||||
|
||||
import flash.geom.Point;
|
||||
import flash.geom.Rectangle;
|
||||
|
||||
import flash.text.TextField;
|
||||
import flash.text.TextFieldAutoSize;
|
||||
import flash.text.TextFormat;
|
||||
|
||||
import flash.events.MouseEvent;
|
||||
|
||||
import mx.core.ClassFactory;
|
||||
|
||||
import as3isolib.display.IsoSprite;
|
||||
import as3isolib.display.primitive.IsoBox;
|
||||
import as3isolib.core.IsoDisplayObject;
|
||||
import as3isolib.geom.Pt;
|
||||
import as3isolib.geom.IsoMath;
|
||||
import as3isolib.display.scene.IsoScene;
|
||||
import as3isolib.display.IsoView;
|
||||
|
||||
import com.threerings.crowd.client.PlaceView;
|
||||
import com.threerings.crowd.data.PlaceObject;
|
||||
import com.threerings.util.ClassUtil;
|
||||
import com.threerings.util.DelayUtil;
|
||||
import com.threerings.util.Log;
|
||||
import com.threerings.util.Map;
|
||||
import com.threerings.util.Maps;
|
||||
import com.threerings.util.MathUtil;
|
||||
import com.threerings.util.Set;
|
||||
import com.threerings.util.Sets;
|
||||
import com.threerings.util.StringUtil;
|
||||
import com.threerings.util.maps.WeakValueMap;
|
||||
import com.threerings.media.Tickable;
|
||||
import com.threerings.media.tile.BaseTile;
|
||||
import com.threerings.media.tile.Colorizer;
|
||||
import com.threerings.media.tile.NoSuchTileSetError;
|
||||
import com.threerings.media.tile.TileSet;
|
||||
import com.threerings.media.tile.TileUtil;
|
||||
import com.threerings.media.util.AStarPathSearch;
|
||||
import com.threerings.media.util.AStarPathSearch_Stepper;
|
||||
import com.threerings.media.util.LineSegmentPath;
|
||||
import com.threerings.media.util.Path;
|
||||
import com.threerings.media.util.Pathable;
|
||||
import com.threerings.media.util.TraversalPred;
|
||||
import com.threerings.miso.client.MisoMetricsTransformation;
|
||||
import com.threerings.miso.client.PrioritizedSceneLayoutRenderer;
|
||||
import com.threerings.miso.data.MisoSceneModel;
|
||||
import com.threerings.miso.data.ObjectInfo;
|
||||
import com.threerings.miso.tile.FringeTile;
|
||||
import com.threerings.miso.util.MisoContext;
|
||||
import com.threerings.miso.util.ObjectSet;
|
||||
import com.threerings.miso.util.MisoSceneMetrics;
|
||||
|
||||
public class MisoScenePanel extends Sprite
|
||||
implements PlaceView, TraversalPred, Tickable
|
||||
{
|
||||
private var log :Log = Log.getLog(MisoScenePanel);
|
||||
|
||||
public function MisoScenePanel (ctx :MisoContext, metrics :MisoSceneMetrics)
|
||||
{
|
||||
_ctx = ctx;
|
||||
// Excitingly, we get to override this globally for as3isolib...
|
||||
IsoMath.transformationObject = new MisoMetricsTransformation(metrics,
|
||||
metrics.tilehei * 3 / 4);
|
||||
|
||||
_metrics = metrics;
|
||||
|
||||
_isoView = new IsoView();
|
||||
_isoView.setSize(DEF_WIDTH, DEF_HEIGHT);
|
||||
|
||||
_centerer = new Centerer(_isoView);
|
||||
|
||||
_vbounds = new Rectangle(0, 0,
|
||||
_isoView.size.x, _isoView.size.y);
|
||||
|
||||
_isoView.addEventListener(MouseEvent.CLICK, onClick);
|
||||
_isoView.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoved);
|
||||
_isoView.addEventListener(MouseEvent.ROLL_OUT, mouseExited);
|
||||
|
||||
addObjectScenes();
|
||||
|
||||
addChild(_loading = createLoadingPanel());
|
||||
|
||||
addEventListener(Event.ADDED_TO_STAGE, addedToStage);
|
||||
addEventListener(Event.REMOVED_FROM_STAGE, removedFromStage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles Event.ADDED_TO_STAGE.
|
||||
*/
|
||||
protected function addedToStage (event :Event) :void
|
||||
{
|
||||
_ctx.getTicker().registerTickable(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles Event.REMOVED_FROM_STAGE.
|
||||
*/
|
||||
protected function removedFromStage (event :Event) :void
|
||||
{
|
||||
_ctx.getTicker().removeTickable(this);
|
||||
}
|
||||
|
||||
public function tick (tickStamp :int) :void
|
||||
{
|
||||
_centerer.tick(tickStamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates whatever we want to show while we're waiting to load our exciting scene tile data.
|
||||
*/
|
||||
protected function createLoadingPanel () :DisplayObject
|
||||
{
|
||||
// By default we just stick up a basic label for this...
|
||||
var loadingText :TextField = new TextField();
|
||||
loadingText.textColor = 0xFFFFFF;
|
||||
var loadingFmt :TextFormat = new TextFormat();
|
||||
loadingFmt.size = 24;
|
||||
loadingText.defaultTextFormat = loadingFmt;
|
||||
loadingText.autoSize = TextFieldAutoSize.LEFT;
|
||||
|
||||
_loadingProgressFunc = function (progress :Number) :void {
|
||||
loadingText.text = "Loading... " + int(progress*100) + "%";
|
||||
loadingText.x = (_isoView.size.x - loadingText.width)/2;
|
||||
loadingText.y = (_isoView.size.y - loadingText.height)/2;
|
||||
|
||||
};
|
||||
_loadingProgressFunc(0.0);
|
||||
return loadingText;
|
||||
}
|
||||
|
||||
protected function loadingProgress (progress :Number) :void
|
||||
{
|
||||
if (_loadingProgressFunc != null) {
|
||||
_loadingProgressFunc(progress);
|
||||
}
|
||||
}
|
||||
|
||||
public function onClick (event :MouseEvent) :void
|
||||
{
|
||||
handleMousePressed(_hobject, event);
|
||||
}
|
||||
|
||||
public function mouseMoved (event :MouseEvent) :void
|
||||
{
|
||||
var viewPt :Point = _isoView.globalToLocal(new Point(event.stageX, event.stageY));
|
||||
|
||||
var x :int = event.stageX;
|
||||
var y :int = event.stageY;
|
||||
|
||||
// give derived classes a chance to start with a hover object
|
||||
var hobject :Object = computeOverHover(x, y);
|
||||
|
||||
// if they came up with nothing, compute the list of objects over
|
||||
// which the mouse is hovering
|
||||
if (hobject == null) {
|
||||
var hits :Array =
|
||||
_objScene.displayListChildren.filter(
|
||||
function(val :Object, idx :int, arr :Array) :Boolean {
|
||||
if (val is PriorityIsoDisplayObject) {
|
||||
return PriorityIsoDisplayObject(val).hitTest(x, y);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
hits.sort(function (v1 :PriorityIsoDisplayObject, v2 :PriorityIsoDisplayObject) :int {
|
||||
// We want reverse order, highest prio first...
|
||||
return v2.getPriority() - v1.getPriority();
|
||||
});
|
||||
|
||||
if (hits.length > 0) {
|
||||
hobject = hits[0];
|
||||
}
|
||||
}
|
||||
|
||||
// if the user isn't hovering over a sprite or object with an
|
||||
// action, allow derived classes to provide some other hover
|
||||
if (hobject == null) {
|
||||
hobject = computeUnderHover(x, y);
|
||||
}
|
||||
|
||||
changeHoverObject(hobject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives derived classes a chance to compute a hover object that takes precedence over sprites
|
||||
* and actionable objects. If this method returns non-null, no sprite or object hover
|
||||
* calculations will be performed and the object returned will become the new hover object.
|
||||
*/
|
||||
protected function computeOverHover (mx :int, my :int) :Object
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives derived classes a chance to compute a hover object that is used if the mouse is not
|
||||
* hovering over a sprite or actionable object. If this method is called, it means that there
|
||||
* are no sprites or objects under the mouse. Thus if it returns non-null, the object returned
|
||||
* will become the new hover object.
|
||||
*/
|
||||
protected function computeUnderHover (mx :int, my :int) :Object
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the hover object to the new object.
|
||||
*/
|
||||
protected function changeHoverObject (newHover :Object) :void
|
||||
{
|
||||
if (newHover == _hobject) {
|
||||
return;
|
||||
}
|
||||
var oldHover :Object = _hobject;
|
||||
_hobject = newHover;
|
||||
hoverObjectChanged(oldHover, newHover);
|
||||
}
|
||||
|
||||
/**
|
||||
* A place for subclasses to react to the hover object changing.
|
||||
* One of the supplied arguments may be null.
|
||||
*/
|
||||
protected function hoverObjectChanged (oldHover :Object, newHover :Object) :void
|
||||
{
|
||||
// Nothing by default.
|
||||
}
|
||||
|
||||
public function mouseExited (event :MouseEvent) :void
|
||||
{
|
||||
// clear the highlight tracking data
|
||||
changeHoverObject(null);
|
||||
}
|
||||
|
||||
public function handleMousePressed (hobject :Object, event :MouseEvent) :Boolean
|
||||
{
|
||||
var viewPt :Point = _isoView.globalToLocal(new Point(event.stageX, event.stageY));
|
||||
moveBy(new Point(viewPt.x - _isoView.width / 2, viewPt.y - _isoView.height / 2), false);
|
||||
return true;
|
||||
}
|
||||
|
||||
public function moveBy (pt :Point, immediate :Boolean) :void
|
||||
{
|
||||
// No scene model yet, just do it...
|
||||
if (_model == null) {
|
||||
_centerer.moveTo(pt.x + _isoView.currentX, pt.y + _isoView.currentY, immediate);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_pendingMoveBy != null) {
|
||||
log.info("Already performing a move...");
|
||||
return;
|
||||
}
|
||||
_pendingMoveBy = pt;
|
||||
|
||||
refreshBaseBlockScenes();
|
||||
}
|
||||
|
||||
public function setSceneModel (model :MisoSceneModel) :void
|
||||
{
|
||||
_model = model;
|
||||
_ctx.getTileManager().ensureLoaded(_model.getAllTilesets(), function() :void {
|
||||
DelayUtil.delayFrame(function() :void {
|
||||
refreshScene();
|
||||
DelayUtil.delayFrame(function() :void {
|
||||
removeChild(_loading);
|
||||
addChild(_isoView);
|
||||
});
|
||||
});
|
||||
}, loadingProgress);
|
||||
}
|
||||
|
||||
public function willEnterPlace (plobj :PlaceObject) :void
|
||||
{
|
||||
}
|
||||
|
||||
public function didLeavePlace (plobj :PlaceObject) :void
|
||||
{
|
||||
}
|
||||
|
||||
/** Computes the fringe tile for the specified coordinate. */
|
||||
public function computeFringeTile (tx :int, ty :int) :BaseTile
|
||||
{
|
||||
return _ctx.getTileManager().getFringer().getFringeTile(_model, tx, ty, _fringes,
|
||||
_masks);
|
||||
}
|
||||
|
||||
protected function getBaseBlocks (buffer :Boolean) :Set
|
||||
{
|
||||
var blocks :Set = Sets.newSetOf(int);
|
||||
|
||||
var size :Point = _isoView.size;
|
||||
|
||||
var xMove :int = (_pendingMoveBy == null ? 0 : _pendingMoveBy.x);
|
||||
var yMove :int = (_pendingMoveBy == null ? 0 : _pendingMoveBy.y);
|
||||
|
||||
var minX :int = xMove - (buffer ? size.x/2 : 0);
|
||||
var maxX :int = size.x + xMove + (buffer ? size.x/2 : 0);
|
||||
var minY :int = yMove - (buffer ? size.y/2 : 0);
|
||||
var maxY :int = BOTTOM_BUFFER + size.y + yMove + (buffer ? size.y/2 : 0);
|
||||
|
||||
var topLeft :Point = _isoView.localToIso(new Point(minX, minY));
|
||||
var topRight :Point = _isoView.localToIso(new Point(maxX, minY));
|
||||
var btmLeft :Point = _isoView.localToIso(new Point(minX, maxY));
|
||||
var btmRight :Point = _isoView.localToIso(new Point(maxX, maxY));
|
||||
|
||||
for (var yy :int = topRight.y - 1; yy <= btmLeft.y + 1; yy++) {
|
||||
for (var xx :int = topLeft.x - 1; xx <= btmRight.x + 1; xx++) {
|
||||
|
||||
// Toss out any that aren't actually in our view.
|
||||
var blkTop :Point = _isoView.isoToLocal(new Pt(xx, yy, 0));
|
||||
var blkRight :Point =
|
||||
_isoView.isoToLocal(new Pt(xx + SceneBlock.BLOCK_SIZE, yy, 0));
|
||||
var blkLeft :Point =
|
||||
_isoView.isoToLocal(new Pt(xx, yy + SceneBlock.BLOCK_SIZE, 0));
|
||||
var blkBtm :Point =
|
||||
_isoView.isoToLocal(new Pt(xx + SceneBlock.BLOCK_SIZE,
|
||||
yy + SceneBlock.BLOCK_SIZE, 0));
|
||||
if (blkTop.y < maxY &&
|
||||
blkBtm.y > minY &&
|
||||
blkLeft.x < maxX &&
|
||||
blkRight.x > minX) {
|
||||
blocks.add(SceneBlock.getBlockKey(xx, yy));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return blocks;
|
||||
}
|
||||
|
||||
protected function createSceneBlock (blockKey :int) :SceneBlock
|
||||
{
|
||||
return new SceneBlock(blockKey, _objScene, _isoView, _metrics);
|
||||
}
|
||||
|
||||
protected function refreshBaseBlockScenes () :void
|
||||
{
|
||||
_resStartTime = getTimer();
|
||||
|
||||
// Clear em til we're done resolving.
|
||||
_pendingPrefetchBlocks = [];
|
||||
|
||||
var blocks :Set = getBaseBlocks(false);
|
||||
|
||||
// Keep this to use in our function...
|
||||
var thisRef :MisoScenePanel = this;
|
||||
|
||||
// Postpone calling complete til they're all queued up.
|
||||
_skipComplete = true;
|
||||
|
||||
blocks.forEach(function(blockKey :int) :void {
|
||||
if (!_blocks.containsKey(blockKey)) {
|
||||
if (_prefetchedBlocks.containsKey(blockKey)) {
|
||||
_readyBlocks.add(_prefetchedBlocks.remove(blockKey));
|
||||
} else {
|
||||
var sceneBlock :SceneBlock = createSceneBlock(blockKey);
|
||||
_pendingBlocks.add(sceneBlock);
|
||||
sceneBlock.resolve(_ctx, _model, thisRef, blockResolved);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
_skipComplete = false;
|
||||
|
||||
|
||||
if (_pendingBlocks.size() == 0) {
|
||||
resolutionComplete();
|
||||
}
|
||||
}
|
||||
|
||||
protected function blockResolved (resolved :SceneBlock) :void
|
||||
{
|
||||
if (!_pendingBlocks.contains(resolved)) {
|
||||
log.info("Trying to resolve non-pending block???: " + resolved.getKey());
|
||||
}
|
||||
// Move that guy from pending to ready...
|
||||
_readyBlocks.add(resolved);
|
||||
_pendingBlocks.remove(resolved);
|
||||
if (_pendingBlocks.size() == 0 && !_skipComplete) {
|
||||
resolutionComplete();
|
||||
}
|
||||
}
|
||||
|
||||
protected function renderObjectScenes () :void
|
||||
{
|
||||
_objScene.render();
|
||||
}
|
||||
|
||||
protected function addObjectScenes () :void
|
||||
{
|
||||
_objScene = new IsoScene();
|
||||
_objScene.layoutRenderer = new ClassFactory(PrioritizedSceneLayoutRenderer);
|
||||
|
||||
_isoView.addScene(_objScene);
|
||||
}
|
||||
|
||||
protected function resolutionComplete () :void
|
||||
{
|
||||
// First, we add in our new blocks...
|
||||
for each (var newBlock :SceneBlock in _readyBlocks.toArray()) {
|
||||
newBlock.render();
|
||||
newBlock.addToCovered(_covered);
|
||||
_blocks.put(newBlock.getKey(), newBlock);
|
||||
}
|
||||
_readyBlocks = Sets.newSetOf(SceneBlock);
|
||||
|
||||
renderObjectScenes();
|
||||
|
||||
// Then we let the scene finally move if it's trying to...
|
||||
if (_pendingMoveBy != null) {
|
||||
_centerer.moveTo(_pendingMoveBy.x + _isoView.currentX,
|
||||
_pendingMoveBy.y + _isoView.currentY, false);
|
||||
_pendingMoveBy = null;
|
||||
}
|
||||
|
||||
// Now, take out any old blocks no longer in our valid blocks.
|
||||
var blocks :Set = getBaseBlocks(true);
|
||||
for each (var baseKey :int in _blocks.keys()) {
|
||||
if (!blocks.contains(baseKey)) {
|
||||
_blocks.remove(baseKey).release();
|
||||
}
|
||||
}
|
||||
// And any prefetch blocks that are bogus can go away too.
|
||||
for each (var preKey :int in _prefetchedBlocks.keys()) {
|
||||
if (!blocks.contains(preKey)) {
|
||||
_prefetchedBlocks.remove(preKey);
|
||||
}
|
||||
}
|
||||
|
||||
log.info("Scene Block Resolution took: " + (getTimer() - _resStartTime) + "ms");
|
||||
|
||||
// Let's setup some prefetching...
|
||||
blocks.forEach(function(blockKey :int) :void {
|
||||
if (!_blocks.containsKey(blockKey)) {
|
||||
var sceneBlock :SceneBlock = createSceneBlock(blockKey);
|
||||
_pendingPrefetchBlocks.push(sceneBlock);
|
||||
}
|
||||
});
|
||||
|
||||
maybePrefetchABlock();
|
||||
}
|
||||
|
||||
protected function maybePrefetchABlock () :void
|
||||
{
|
||||
if (_pendingPrefetchBlocks.length != 0) {
|
||||
_pendingPrefetchBlocks.shift().resolve(_ctx, _model, this, prefetchBlockResolved);
|
||||
}
|
||||
}
|
||||
|
||||
protected function prefetchBlockResolved (resolved :SceneBlock) :void
|
||||
{
|
||||
_prefetchedBlocks.put(resolved.getKey(), resolved);
|
||||
|
||||
// If we haven't moved on to resolve a new region, let's try another block.
|
||||
if (_pendingBlocks.size() == 0) {
|
||||
DelayUtil.delayFrame(function() :void {
|
||||
maybePrefetchABlock();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected function refreshScene () :void
|
||||
{
|
||||
// Clear it out...
|
||||
_isoView.removeAllScenes();
|
||||
|
||||
refreshBaseBlockScenes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Derived classes can override this method and provide a colorizer that will be used to
|
||||
* colorize the supplied scene object when rendering.
|
||||
*/
|
||||
public function getColorizer (oinfo :ObjectInfo) :Colorizer
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public function canTraverse (traverser :Object, tx :int, ty :int) :Boolean
|
||||
{
|
||||
var block :SceneBlock = _blocks.get(SceneBlock.getBlockKey(tx, ty));
|
||||
var baseTraversable :Boolean = (block == null) ? canTraverseUnresolved(traverser, tx, ty) :
|
||||
block.canTraverseBase(traverser, tx, ty);
|
||||
|
||||
return baseTraversable && !_covered.contains(StringUtil.toCoordsString(tx, ty));
|
||||
}
|
||||
|
||||
/**
|
||||
* Derived classes can control whether or not we consider unresolved tiles to be traversable
|
||||
* or not.
|
||||
*/
|
||||
protected function canTraverseUnresolved (traverser :Object, tx :int, ty :int) :Boolean
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes a path for the specified sprite to the specified tile coordinates.
|
||||
*
|
||||
* @param loose if true, an approximate path will be returned if a complete path cannot be
|
||||
* located. This path will navigate the sprite "legally" as far as possible and then walk the
|
||||
* sprite in a straight line to its final destination. This is generally only useful if the
|
||||
* the path goes "off screen".
|
||||
*/
|
||||
public function getPath (sprite :IsoDisplayObject, x :int, y :int, loose :Boolean) :Path
|
||||
{
|
||||
// sanity check
|
||||
if (sprite == null) {
|
||||
throw new Error("Can't get path for null sprite [x=" + x + ", y=" + y + ".");
|
||||
}
|
||||
|
||||
// compute our longest path from the screen size
|
||||
var longestPath :int = 3 * (width / _metrics.tilewid);
|
||||
|
||||
// get a reasonable tile path through the scene
|
||||
var start :int = getTimer();
|
||||
var search :AStarPathSearch = new AStarPathSearch(this, new AStarPathSearch_Stepper());
|
||||
var points :Array = search.getPath(sprite, longestPath, int(Math.round(sprite.x)),
|
||||
int(Math.round(sprite.y)), x, y, loose);
|
||||
|
||||
// Replace the starting point with the Number values rather than the rounded version...
|
||||
if (points != null) {
|
||||
points[0] = new Point(sprite.x, sprite.y);
|
||||
}
|
||||
|
||||
var duration :int = getTimer() - start;
|
||||
|
||||
// sanity check the number of nodes searched so that we can keep an eye out for bogosity
|
||||
if (duration > 500) {
|
||||
log.warning("Considered a lot of nodes for path from " +
|
||||
StringUtil.toCoordsString(sprite.x, sprite.y) + " to " +
|
||||
StringUtil.toCoordsString(x, y) +
|
||||
" [duration=" + duration + "].");
|
||||
}
|
||||
|
||||
// construct a path object to guide the sprite on its merry way
|
||||
return (points == null) ? null : LineSegmentPath.createWithList(points);
|
||||
}
|
||||
|
||||
protected var _model :MisoSceneModel;
|
||||
|
||||
protected var _isoView :IsoView;
|
||||
|
||||
protected var _ctx :MisoContext;
|
||||
|
||||
protected var _metrics :MisoSceneMetrics;
|
||||
|
||||
/** What we display while we're loading up our tilesets. */
|
||||
protected var _loading :DisplayObject;
|
||||
|
||||
/** If we should do something when we hear about progress updates, this is it. */
|
||||
protected var _loadingProgressFunc :Function;
|
||||
|
||||
protected var _objScene :IsoScene;
|
||||
|
||||
/** All of the active blocks that are part of the scene. */
|
||||
protected var _blocks :Map = Maps.newMapOf(int);
|
||||
|
||||
/** If we're resolving in preparation for moving, this is how much we'll move by when ready. */
|
||||
protected var _pendingMoveBy :Point;
|
||||
|
||||
/** Required blocks we're working on resolving. */
|
||||
protected var _pendingBlocks :Set = Sets.newSetOf(SceneBlock);
|
||||
|
||||
/** Blocks that are resolved and ready for adding to the scene. */
|
||||
protected var _readyBlocks :Set = Sets.newSetOf(SceneBlock);
|
||||
|
||||
/** The queue of blocks we'd like to prefetch when we have a chance. */
|
||||
protected var _pendingPrefetchBlocks :Array = [];
|
||||
|
||||
/** The blocks already prefetched and ready to go if we need them. */
|
||||
protected var _prefetchedBlocks :Map = Maps.newMapOf(int);
|
||||
|
||||
protected var _masks :Map = Maps.newMapOf(int);
|
||||
protected var _fringes :Map = new WeakValueMap(Maps.newMapOf(FringeTile));
|
||||
|
||||
/** What time did we start the current scene resolution. */
|
||||
protected var _resStartTime :int;
|
||||
|
||||
/** If any block happens to resolve should we currently skip calling completion. */
|
||||
protected var _skipComplete :Boolean
|
||||
|
||||
/** Info on the object that the mouse is currently hovering over. */
|
||||
protected var _hobject :Object;
|
||||
|
||||
protected var _vbounds :Rectangle;
|
||||
|
||||
protected var _covered :Set = Sets.newSetOf(String);
|
||||
|
||||
protected var _centerer :Centerer;
|
||||
|
||||
protected const DEF_WIDTH :int = 985;
|
||||
protected const DEF_HEIGHT :int = 560;
|
||||
|
||||
protected const BOTTOM_BUFFER :int = 300;
|
||||
}
|
||||
}
|
||||
|
||||
import as3isolib.display.IsoView;
|
||||
import as3isolib.geom.Pt;
|
||||
|
||||
import com.threerings.media.util.LineSegmentPath;
|
||||
import com.threerings.media.util.Path;
|
||||
import com.threerings.media.util.Pathable;
|
||||
import com.threerings.media.Tickable;
|
||||
|
||||
import com.threerings.util.DirectionCodes;
|
||||
|
||||
|
||||
class Centerer
|
||||
implements Pathable, Tickable
|
||||
{
|
||||
public function Centerer (isoView :IsoView)
|
||||
{
|
||||
_isoView = isoView;
|
||||
}
|
||||
|
||||
public function moveTo (x :int, y:int, immediate :Boolean) :void
|
||||
{
|
||||
if (immediate) {
|
||||
cancelMove();
|
||||
setLocation(x, y);
|
||||
} else {
|
||||
var path :LineSegmentPath =
|
||||
LineSegmentPath.createWithInts(_isoView.currentX, _isoView.currentY, x, y);
|
||||
path.setVelocity(SCROLL_VELOCITY);
|
||||
move(path);
|
||||
}
|
||||
}
|
||||
|
||||
public function move (path :Path) :void
|
||||
{
|
||||
// if there's a previous path, let it know that it's going away
|
||||
cancelMove();
|
||||
|
||||
// save off this path
|
||||
_path = path;
|
||||
|
||||
// we'll initialize it on our next tick thanks to a zero path stamp
|
||||
_pathStamp = 0;
|
||||
}
|
||||
|
||||
public function cancelMove () :void
|
||||
{
|
||||
if (_path != null) {
|
||||
var oldpath :Path = _path;
|
||||
_path = null;
|
||||
oldpath.wasRemoved(this);
|
||||
}
|
||||
}
|
||||
|
||||
public function tick (tickStamp :int) :void
|
||||
{
|
||||
if (_path != null) {
|
||||
if (_pathStamp == 0) {
|
||||
_pathStamp = tickStamp
|
||||
_path.init(this, _pathStamp);
|
||||
}
|
||||
_path.tick(this, tickStamp);
|
||||
}
|
||||
}
|
||||
|
||||
public function getX () :Number
|
||||
{
|
||||
return _isoView.currentX;
|
||||
}
|
||||
|
||||
public function getY () :Number
|
||||
{
|
||||
return _isoView.currentY;
|
||||
}
|
||||
|
||||
public function setLocation (x :Number, y :Number) :void
|
||||
{
|
||||
_isoView.centerOnPt(new Pt(x, y), false);
|
||||
}
|
||||
|
||||
public function setOrientation (orient :int) :void
|
||||
{
|
||||
}
|
||||
|
||||
public function getOrientation () :int
|
||||
{
|
||||
return DirectionCodes.NONE;
|
||||
}
|
||||
|
||||
public function pathBeginning () :void
|
||||
{
|
||||
}
|
||||
|
||||
public function pathCompleted (timestamp :int) :void
|
||||
{
|
||||
_path = null;
|
||||
}
|
||||
|
||||
protected var _isoView :IsoView;
|
||||
protected var _path :Path;
|
||||
protected var _pathStamp :int;
|
||||
|
||||
/** Our scroll path velocity. */
|
||||
protected static const SCROLL_VELOCITY :Number = 0.4;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.miso.client {
|
||||
|
||||
import com.threerings.media.tile.ObjectTile;
|
||||
import com.threerings.media.tile.Tile;
|
||||
import com.threerings.miso.util.MisoSceneMetrics;
|
||||
|
||||
public class ObjectTileIsoSprite extends TileIsoSprite
|
||||
{
|
||||
public function ObjectTileIsoSprite (x :int, y :int, tileId :int, tile :Tile,
|
||||
priority :int, metrics :MisoSceneMetrics)
|
||||
{
|
||||
super(x, y, tileId, tile, ObjectTile(tile).getPriority() == 0 ?
|
||||
priority : ObjectTile(tile).getPriority(), metrics);
|
||||
}
|
||||
|
||||
public override function layout (x :int, y :int, tile :Tile) :void
|
||||
{
|
||||
super.layout(x, y, tile);
|
||||
|
||||
setSize(tile.getBaseWidth(), tile.getBaseHeight(), 1);
|
||||
moveBy(-(tile.getBaseWidth() - 1), -(tile.getBaseHeight() - 1), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.miso.client {
|
||||
|
||||
import flash.utils.Dictionary;
|
||||
import flash.utils.getTimer;
|
||||
|
||||
import as3isolib.display.renderers.ISceneLayoutRenderer;
|
||||
import as3isolib.bounds.IBounds;
|
||||
import as3isolib.core.as3isolib_internal;
|
||||
import as3isolib.core.IsoDisplayObject;
|
||||
import as3isolib.display.scene.IIsoScene;
|
||||
|
||||
/**
|
||||
* Adapted from the as3isolib DefaultSceneLayoutRenderer, but thanks to its private vars, I cannot
|
||||
* simply extend that class.
|
||||
*/
|
||||
public class PrioritizedSceneLayoutRenderer
|
||||
implements ISceneLayoutRenderer
|
||||
{
|
||||
/**
|
||||
* Places all of our children in their appropriate rendering order based on their iso bounds
|
||||
* and as necessary, by priority. Priority only comes into play if the iso bounds overlap.
|
||||
*/
|
||||
public function renderScene (scene:IIsoScene):void
|
||||
{
|
||||
// This is a little brute-force. Some suggestions from DefaultSceneLayoutRenderer:
|
||||
// - cache dependencies between frames, only adjust invalidated objects, keeping old
|
||||
// ordering as best as possible
|
||||
// - screen space subdivision to limit dependency scan
|
||||
// - set the invalidated children first, then do a rescan to make sure everything else is
|
||||
// where it needs to be, too? probably need to order the invalidated children sets from
|
||||
// low to high index
|
||||
|
||||
// Reset everything for this rendering pass.
|
||||
_scene = scene;
|
||||
_dependencies = new Dictionary();
|
||||
_depth = 0;
|
||||
_visited = new Dictionary();
|
||||
|
||||
var startTime:uint = getTimer();
|
||||
|
||||
// Use the non-rearranging display list so that the dependency sort will tend to
|
||||
// create similar output each pass
|
||||
var children:Array = _scene.displayListChildren;
|
||||
|
||||
// Full naive cartesian scan, see what objects are behind/in front of child[ii]
|
||||
var max:uint = children.length;
|
||||
for (var ii :uint = 0; ii < max; ii++)
|
||||
{
|
||||
var objA:IsoDisplayObject = children[ii];
|
||||
|
||||
var rightA:Number = objA.x + objA.width;
|
||||
var frontA:Number = objA.y + objA.length;
|
||||
var topA:Number = objA.z + objA.height;
|
||||
|
||||
var prioA :int = objA is PriorityIsoDisplayObject ?
|
||||
PriorityIsoDisplayObject(objA).getPriority() : 0;
|
||||
|
||||
for (var jj :uint = ii + 1; jj < max; jj++)
|
||||
{
|
||||
var objB:IsoDisplayObject = children[jj];
|
||||
|
||||
var prioB :int = objB is PriorityIsoDisplayObject ?
|
||||
PriorityIsoDisplayObject(objB).getPriority() : 0;
|
||||
|
||||
var rightB:Number = objB.x + objB.width;
|
||||
var frontB:Number = objB.y + objB.length;
|
||||
var topB:Number = objB.z + objB.height;
|
||||
|
||||
// See if B should go behind A and vice-versa.
|
||||
var bBehindA :Boolean = ((objB.x < rightA) &&
|
||||
(objB.y < frontA) &&
|
||||
(objB.z < topA));
|
||||
var aBehindB :Boolean = ((objA.x < rightB) &&
|
||||
(objA.y < frontB) &&
|
||||
(objA.z < topB));
|
||||
|
||||
if ((bBehindA && aBehindB)) {
|
||||
// Overlap means we need to use the priority...
|
||||
if (prioA > prioB) {
|
||||
addDependency(objA, objB);
|
||||
} else {
|
||||
addDependency(objB, objA);
|
||||
}
|
||||
} else if (bBehindA) {
|
||||
addDependency(objA, objB);
|
||||
} else if (aBehindB) {
|
||||
addDependency(objB, objA);
|
||||
}
|
||||
// Note - if we find that neither is behind the other, we don't add a dependency
|
||||
// ordering. If they actually do visually overlap due to sticking outside their
|
||||
// bounds, this can sometimes cause inconsistent visuals.
|
||||
}
|
||||
}
|
||||
|
||||
//trace("dependency scan time", getTimer() - startTime, "ms");
|
||||
|
||||
// Set the childrens' depth, using dependency ordering
|
||||
for each (var obj:IsoDisplayObject in children) {
|
||||
if (!_visited[obj]) {
|
||||
place(obj);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear these out so we're not retaining memory between calls
|
||||
_visited = null;
|
||||
_dependencies = null;
|
||||
|
||||
//trace("scene layout render time", getTimer() - startTime, "ms (manual sort)");
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the front-to-back dependency to our map of all such dependencies.
|
||||
*/
|
||||
protected function addDependency (front :IsoDisplayObject, back :IsoDisplayObject) :void
|
||||
{
|
||||
var deps :Array = _dependencies[front];
|
||||
if (deps == null) {
|
||||
deps = [];
|
||||
_dependencies[front] = deps;
|
||||
}
|
||||
deps.push(back);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dependency-ordered depth placement of the given objects and its dependencies.
|
||||
*/
|
||||
protected function place (obj:IsoDisplayObject):void
|
||||
{
|
||||
_visited[obj] = true;
|
||||
|
||||
for each (var inner:IsoDisplayObject in _dependencies[obj]) {
|
||||
if (!_visited[inner]) {
|
||||
place(inner);
|
||||
}
|
||||
}
|
||||
|
||||
if (_depth != obj.depth)
|
||||
{
|
||||
_scene.setChildIndex(obj, _depth);
|
||||
}
|
||||
_depth++;
|
||||
};
|
||||
|
||||
/** The depth we're currently placing children at. */
|
||||
protected var _depth :uint;
|
||||
|
||||
/** Any objects we've already placed. */
|
||||
protected var _visited :Dictionary;
|
||||
|
||||
/** The scene on which we're operating. */
|
||||
protected var _scene :IIsoScene;
|
||||
|
||||
/** All the front-back dependencies we know about. This maps an object to a list of all
|
||||
* the objects that should be rendered behind (and therefore before) it. */
|
||||
protected var _dependencies :Dictionary;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.miso.client {
|
||||
|
||||
/**
|
||||
* Any Iso object wishing to specify a render priority should do so by implementing this interface.
|
||||
*/
|
||||
public interface PriorityIsoDisplayObject
|
||||
{
|
||||
/**
|
||||
* Returns the render priority for the display object.
|
||||
*/
|
||||
function getPriority () :int;
|
||||
|
||||
/**
|
||||
* Returns whether the location on the object should register a hit if clicked.
|
||||
*/
|
||||
function hitTest (stageX :int, stageY :int) :Boolean;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,300 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.miso.client {
|
||||
|
||||
import flash.geom.Point;
|
||||
import flash.geom.Rectangle;
|
||||
|
||||
import as3isolib.display.scene.IsoScene;
|
||||
import as3isolib.display.IsoView;
|
||||
|
||||
import com.threerings.util.Log;
|
||||
import com.threerings.util.MathUtil;
|
||||
import com.threerings.util.Set;
|
||||
import com.threerings.util.StringUtil;
|
||||
|
||||
import com.threerings.media.tile.BaseTile;
|
||||
import com.threerings.media.tile.Colorizer;
|
||||
import com.threerings.media.tile.NoSuchTileSetError;
|
||||
import com.threerings.media.tile.Tile;
|
||||
import com.threerings.media.tile.TileSet;
|
||||
import com.threerings.media.tile.TileUtil;
|
||||
import com.threerings.miso.client.BaseTileIsoSprite;
|
||||
import com.threerings.miso.client.MisoScenePanel;
|
||||
import com.threerings.miso.client.ObjectTileIsoSprite;
|
||||
import com.threerings.miso.data.MisoSceneModel;
|
||||
import com.threerings.miso.data.ObjectInfo;
|
||||
import com.threerings.miso.util.MisoContext;
|
||||
import com.threerings.miso.util.MisoSceneMetrics;
|
||||
import com.threerings.miso.util.ObjectSet;
|
||||
|
||||
public class SceneBlock
|
||||
{
|
||||
private var log :Log = Log.getLog(SceneBlock);
|
||||
|
||||
public static const BLOCK_SIZE :int = 4;
|
||||
|
||||
public function SceneBlock (key :int, objScene :IsoScene, isoView :IsoView,
|
||||
metrics :MisoSceneMetrics)
|
||||
{
|
||||
_key = key;
|
||||
_objScene = objScene;
|
||||
_isoView = isoView;
|
||||
_metrics = metrics;
|
||||
}
|
||||
|
||||
public function getKey () :int
|
||||
{
|
||||
return _key;
|
||||
}
|
||||
|
||||
public static function getBlockKey (x :int, y :int) :int
|
||||
{
|
||||
return (MathUtil.floorDiv(x, BLOCK_SIZE) << 16 |
|
||||
(MathUtil.floorDiv(y, BLOCK_SIZE) & 0xFFFF));
|
||||
}
|
||||
|
||||
public static function getBlockX (key :int) :int
|
||||
{
|
||||
return (key >> 16) * BLOCK_SIZE;
|
||||
}
|
||||
|
||||
public static function getBlockY (key :int) :int
|
||||
{
|
||||
// We really do mean to do this crazy shift left then back right thing to get our sign
|
||||
// back from before we encoded.
|
||||
return (((key & 0xFFFF) * BLOCK_SIZE) << 16) >> 16;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts getting all the tiles for the block loaded up and ready to go.
|
||||
*/
|
||||
public function resolve (ctx :MisoContext, model :MisoSceneModel, panel :MisoScenePanel,
|
||||
completeCallback :Function) :void
|
||||
{
|
||||
_completeCallback = completeCallback;
|
||||
|
||||
var x :int = getBlockX(_key);
|
||||
var y :int = getBlockY(_key);
|
||||
|
||||
_baseScene = new IsoScene();
|
||||
_baseScene.layoutEnabled = false;
|
||||
for (var ii :int = x; ii < x + BLOCK_SIZE; ii++) {
|
||||
for (var jj :int = y; jj < y + BLOCK_SIZE; jj++) {
|
||||
var tileId :int = model.getBaseTileId(ii, jj);
|
||||
if (tileId <= 0) {
|
||||
var defSet :TileSet;
|
||||
try {
|
||||
var setId :int = model.getDefaultBaseTileSet();
|
||||
defSet = ctx.getTileManager().getTileSet(setId);
|
||||
tileId = TileUtil.getFQTileId(setId,
|
||||
TileUtil.getTileHash(ii, jj) % defSet.getTileCount());
|
||||
} catch (err :NoSuchTileSetError) {
|
||||
// Someone else already complained...
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
var tileSet :TileSet;
|
||||
try {
|
||||
tileSet =
|
||||
ctx.getTileManager().getTileSet(TileUtil.getTileSetId(tileId));
|
||||
} catch (err :NoSuchTileSetError) {
|
||||
// Someone else already complained...
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tileSet == null) {
|
||||
log.warning("TileManager returned null tilset: " +
|
||||
TileUtil.getTileSetId(tileId));
|
||||
continue;
|
||||
}
|
||||
|
||||
createBaseSprite(tileSet, ii, jj, tileId, panel);
|
||||
}
|
||||
}
|
||||
|
||||
// And now grab the appropriate objects.
|
||||
var set :ObjectSet = new ObjectSet();
|
||||
model.getObjects(new Rectangle(x, y, BLOCK_SIZE, BLOCK_SIZE), set);
|
||||
_objSprites = [];
|
||||
for (ii = 0; ii < set.size(); ii++) {
|
||||
var objInfo :ObjectInfo = set.get(ii);
|
||||
var objTileId :int = objInfo.tileId;
|
||||
var objTileSet :TileSet;
|
||||
try {
|
||||
objTileSet =
|
||||
ctx.getTileManager().getTileSet(TileUtil.getTileSetId(objTileId));
|
||||
} catch (err :NoSuchTileSetError) {
|
||||
// Someone else already complained...
|
||||
continue;
|
||||
}
|
||||
|
||||
if (objTileSet == null) {
|
||||
log.warning("TileManager returned null TileSet: " +
|
||||
TileUtil.getTileSetId(objTileId));
|
||||
continue;
|
||||
}
|
||||
|
||||
createObjSprite(objTileSet, objInfo, panel);
|
||||
}
|
||||
|
||||
maybeLoaded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually adds all our tiles as sprites to the scenes.
|
||||
*/
|
||||
public function render () :void
|
||||
{
|
||||
_isoView.addSceneAt(_baseScene, 0);
|
||||
_baseScene.render();
|
||||
for each (var sprite :ObjectTileIsoSprite in _objSprites) {
|
||||
_objScene.addChild(sprite);
|
||||
}
|
||||
}
|
||||
|
||||
public function addToCovered (covered :Set) :void
|
||||
{
|
||||
for each (var sprite :ObjectTileIsoSprite in _objSprites) {
|
||||
for (var xx :int = sprite.x; xx < sprite.x + sprite.width; xx++) {
|
||||
for (var yy :int = sprite.y; yy < sprite.y + sprite.length; yy++) {
|
||||
covered.add(StringUtil.toCoordsString(xx, yy));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes our tiles' sprites from the scenes.
|
||||
*/
|
||||
public function release () :void
|
||||
{
|
||||
_isoView.removeScene(_baseScene);
|
||||
|
||||
for each (var sprite :ObjectTileIsoSprite in _objSprites) {
|
||||
_objScene.removeChild(sprite);
|
||||
}
|
||||
}
|
||||
|
||||
protected function createBaseSprite (tileSet :TileSet, x :int, y :int, tileId :int,
|
||||
panel :MisoScenePanel) :void
|
||||
{
|
||||
var fringeTile :BaseTile = panel.computeFringeTile(x, y);
|
||||
|
||||
var tile :BaseTile = BaseTile(tileSet.getTile(TileUtil.getTileIndex(tileId)));
|
||||
|
||||
var bx :int = getBlockX(_key);
|
||||
var by :int = getBlockY(_key);
|
||||
_passable[(y - by) * BLOCK_SIZE + (x - bx)] =
|
||||
tile.isPassable() && (fringeTile == null || fringeTile.isPassable());
|
||||
|
||||
if (tile.getImage() != null && (fringeTile == null || fringeTile.getImage() != null)) {
|
||||
_baseScene.addChild(new BaseTileIsoSprite(x, y, tileId, tile, _metrics, fringeTile));
|
||||
} else {
|
||||
noteTileToLoad();
|
||||
|
||||
var maybeLoaded :Function = function (loaded :Tile) :void {
|
||||
if (tile.getImage() != null &&
|
||||
(fringeTile == null || fringeTile.getImage() != null)) {
|
||||
_baseScene.addChild(new BaseTileIsoSprite(x, y, tileId, tile, _metrics,
|
||||
fringeTile));
|
||||
noteTileLoaded();
|
||||
}
|
||||
};
|
||||
|
||||
if (tile.getImage() == null) {
|
||||
tile.notifyOnLoad(maybeLoaded);
|
||||
}
|
||||
if (fringeTile != null && fringeTile.getImage() == null) {
|
||||
fringeTile.notifyOnLoad(maybeLoaded);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function createObjSprite (tileSet :TileSet, objInfo :ObjectInfo,
|
||||
panel :MisoScenePanel) :void
|
||||
{
|
||||
var tile :Tile = tileSet.getTile(TileUtil.getTileIndex(objInfo.tileId),
|
||||
panel.getColorizer(objInfo));
|
||||
|
||||
if (tile.getImage() != null) {
|
||||
_objSprites.push(new ObjectTileIsoSprite(objInfo.x, objInfo.y, objInfo.tileId, tile,
|
||||
objInfo.priority, _metrics));
|
||||
} else {
|
||||
noteTileToLoad();
|
||||
|
||||
tile.notifyOnLoad(function (tile :Tile) :void {
|
||||
_objSprites.push(new ObjectTileIsoSprite(objInfo.x, objInfo.y, objInfo.tileId, tile,
|
||||
objInfo.priority, _metrics));
|
||||
noteTileLoaded();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the base & fringe tiles are traversable here.
|
||||
*/
|
||||
public function canTraverseBase (traverser :Object, tx :int, ty :int) :Boolean
|
||||
{
|
||||
// Only handles the base tiles since the objects cross multiple scene blocks...
|
||||
var bx :int = getBlockX(_key);
|
||||
var by :int = getBlockY(_key);
|
||||
|
||||
return _passable[(ty - by) * BLOCK_SIZE + (tx - bx)];
|
||||
}
|
||||
|
||||
protected function noteTileToLoad () :void
|
||||
{
|
||||
_pendingCt++;
|
||||
}
|
||||
|
||||
protected function noteTileLoaded () :void
|
||||
{
|
||||
_pendingCt--;
|
||||
maybeLoaded();
|
||||
}
|
||||
|
||||
protected function maybeLoaded () :void
|
||||
{
|
||||
if (_pendingCt == 0) {
|
||||
_completeCallback(this);
|
||||
_completeCallback = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected var _passable :Array = new Array(BLOCK_SIZE * BLOCK_SIZE);
|
||||
|
||||
protected var _baseScene :IsoScene;
|
||||
protected var _objSprites :Array;
|
||||
|
||||
protected var _key :int;
|
||||
|
||||
protected var _completeCallback :Function;
|
||||
|
||||
protected var _pendingCt :int;
|
||||
|
||||
protected var _objScene :IsoScene;
|
||||
protected var _isoView :IsoView;
|
||||
protected var _metrics :MisoSceneMetrics;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.miso.client {
|
||||
|
||||
import flash.display.Bitmap;
|
||||
import flash.display.DisplayObject;
|
||||
|
||||
import flash.geom.Point;
|
||||
|
||||
import as3isolib.core.IsoDisplayObject;
|
||||
import as3isolib.display.IsoSprite;
|
||||
import as3isolib.display.primitive.IsoBox;
|
||||
import as3isolib.graphics.SolidColorFill;
|
||||
|
||||
import com.threerings.media.tile.Tile;
|
||||
import com.threerings.media.tile.TileSet;
|
||||
import com.threerings.media.tile.TileUtil;
|
||||
import com.threerings.util.Log;
|
||||
import com.threerings.miso.util.MisoSceneMetrics;
|
||||
|
||||
public class TileIsoSprite extends IsoSprite
|
||||
implements PriorityIsoDisplayObject
|
||||
{
|
||||
private static var log :Log = Log.getLog(TileIsoSprite);
|
||||
|
||||
public function TileIsoSprite (x :int, y :int, tileId :int, tile :Tile, priority :int,
|
||||
metrics :MisoSceneMetrics, fringe :Tile = null)
|
||||
{
|
||||
_tileId = tileId;
|
||||
_metrics = metrics;
|
||||
_priority = priority;
|
||||
_fringe = fringe;
|
||||
|
||||
layout(x, y, tile);
|
||||
|
||||
setSize(tile.getBaseWidth(), tile.getBaseHeight(), 1);
|
||||
|
||||
if (tile.getImage() == null) {
|
||||
tile.notifyOnLoad(function() :void {
|
||||
gotTileImage(tile);
|
||||
});
|
||||
} else {
|
||||
gotTileImage(tile);
|
||||
}
|
||||
}
|
||||
|
||||
public function layout (x :int, y :int, tile :Tile) :void
|
||||
{
|
||||
moveTo(x, y, 0);
|
||||
}
|
||||
|
||||
protected function gotTileImage (tile :Tile) :void
|
||||
{
|
||||
var image :DisplayObject = tile.getImage();
|
||||
// as3isolib uses top instead of bottom.
|
||||
image.x = -tile.getOriginX() + _metrics.tilewid *
|
||||
((tile.getBaseWidth() - tile.getBaseHeight())/2);
|
||||
image.y = -tile.getOriginY() + _metrics.tilehei *
|
||||
((tile.getBaseWidth() + tile.getBaseHeight())/2);
|
||||
|
||||
if (_fringe == null) {
|
||||
sprites = [image];
|
||||
} else {
|
||||
var fringeImg :DisplayObject = _fringe.getImage();
|
||||
// as3isolib uses top instead of bottom.
|
||||
fringeImg.x = -_fringe.getOriginX() + _metrics.tilewid *
|
||||
((_fringe.getBaseWidth() - _fringe.getBaseHeight())/2);
|
||||
fringeImg.y = -_fringe.getOriginY() + _metrics.tilehei *
|
||||
((_fringe.getBaseWidth() + _fringe.getBaseHeight())/2);
|
||||
|
||||
sprites = [image, fringeImg];
|
||||
}
|
||||
render();
|
||||
}
|
||||
|
||||
public function getPriority () :int
|
||||
{
|
||||
return _priority;
|
||||
}
|
||||
|
||||
public function hitTest (stageX :int, stageY :int) :Boolean
|
||||
{
|
||||
if (sprites == null || sprites.length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sprites[0] is Bitmap) {
|
||||
if (!sprites[0].hitTestPoint(stageX, stageY, true)) {
|
||||
// Doesn't even hit the bounds...
|
||||
return false;
|
||||
}
|
||||
// Check the actual pixels...
|
||||
var pt :Point = sprites[0].globalToLocal(new Point(stageX, stageY));
|
||||
return Bitmap(sprites[0]).bitmapData.hitTest(new Point(0, 0), 0, pt);
|
||||
} else {
|
||||
return sprites[0].hitTestPoint(stageX, stageY, true);
|
||||
}
|
||||
}
|
||||
|
||||
public function toString () :String
|
||||
{
|
||||
return x + ", " + y + ": " + _tileId;
|
||||
}
|
||||
|
||||
protected var _tileId :int;
|
||||
|
||||
protected var _priority :int;
|
||||
|
||||
protected var _fringe :Tile;
|
||||
|
||||
protected var _metrics :MisoSceneMetrics;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.miso.data {
|
||||
|
||||
import flash.geom.Rectangle;
|
||||
|
||||
import com.threerings.miso.util.ObjectSet;
|
||||
import com.threerings.io.SimpleStreamableObject;
|
||||
import com.threerings.miso.data.MisoSceneModel;
|
||||
import com.threerings.io.ObjectInputStream;
|
||||
import com.threerings.miso.data.ObjectInfo;
|
||||
import com.threerings.util.ClassUtil;
|
||||
import com.threerings.util.Cloneable;
|
||||
import com.threerings.util.Set;
|
||||
import com.threerings.io.ObjectOutputStream;
|
||||
|
||||
/**
|
||||
* Contains basic information for a miso scene model that is shared among
|
||||
* the specialized model implementations.
|
||||
*/
|
||||
public /*abstract*/ class MisoSceneModel extends SimpleStreamableObject
|
||||
implements Cloneable
|
||||
{
|
||||
public function clone () :Object
|
||||
{
|
||||
return (ClassUtil.newInstance(this) as MisoSceneModel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the fully qualified tile id of the base tile at the
|
||||
* specified coordinates. <code>-1</code> will be returned if there is
|
||||
* no tile at the specified coordinate.
|
||||
*/
|
||||
public function getBaseTileId (x :int, y :int) :int
|
||||
{
|
||||
throw new Error("abstract");
|
||||
}
|
||||
|
||||
public function setBaseTile (x :int, y :int, tileId :int) :Boolean
|
||||
{
|
||||
throw new Error("abstract");
|
||||
}
|
||||
|
||||
public function setDefaultBaseTileSet (tileSetId :int) :void
|
||||
{
|
||||
// nothing doing
|
||||
}
|
||||
|
||||
/**
|
||||
* Scene models can return a default tileset to be used when no base
|
||||
* tile data exists for a particular tile.
|
||||
*/
|
||||
public function getDefaultBaseTileSet () :int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the supplied object set with info on all objects whose
|
||||
* origin falls in the requested region.
|
||||
*/
|
||||
public function getObjects (region :Rectangle, set :ObjectSet) :void
|
||||
{
|
||||
throw new Error("abstract");
|
||||
}
|
||||
|
||||
public function addObject (info :ObjectInfo) :Boolean
|
||||
{
|
||||
throw new Error("abstract");
|
||||
}
|
||||
|
||||
public function updateObject (info :ObjectInfo) :void
|
||||
{
|
||||
throw new Error("abstract");
|
||||
}
|
||||
|
||||
public function removeObject (info :ObjectInfo) :Boolean
|
||||
{
|
||||
throw new Error("abstract");
|
||||
}
|
||||
|
||||
public function getAllTilesets () :Set
|
||||
{
|
||||
throw new Error("abstract");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.miso.data {
|
||||
|
||||
import com.threerings.util.Hashable;
|
||||
import com.threerings.util.ClassUtil;
|
||||
import com.threerings.util.Cloneable;
|
||||
import com.threerings.util.StringUtil;
|
||||
|
||||
import com.threerings.io.SimpleStreamableObject;
|
||||
import com.threerings.io.ObjectInputStream;
|
||||
import com.threerings.io.ObjectOutputStream;
|
||||
|
||||
import com.threerings.miso.data.ObjectInfo;
|
||||
import com.threerings.media.tile.TileUtil;
|
||||
|
||||
/**
|
||||
* Contains information about an object in a Miso scene.
|
||||
*/
|
||||
public class ObjectInfo extends SimpleStreamableObject
|
||||
implements Cloneable, Hashable
|
||||
{
|
||||
/** The fully qualified object tile id. */
|
||||
public var tileId :int;
|
||||
|
||||
/** The x and y tile coordinates of the object. */
|
||||
public var x :int;
|
||||
public var y :int;
|
||||
|
||||
/** Don't access this directly unless you are serializing this
|
||||
* instance. Use {@link #getPriority} instead. */
|
||||
public var priority :int = 0;
|
||||
|
||||
/** The action associated with this object or null if it has no
|
||||
* action. */
|
||||
public var action :String;
|
||||
|
||||
/** A "spot" associated with this object (specified as an offset from
|
||||
* the fine coordinates of the object's origin tile). */
|
||||
public var sx :int;
|
||||
public var sy :int;
|
||||
|
||||
/** The orientation of the "spot" associated with this object. */
|
||||
public var sorient :int;
|
||||
|
||||
/** Up to two colorization assignments for this object. */
|
||||
public var zations :int;
|
||||
|
||||
public function ObjectInfo (tileId :int = 0, x :int = 0, y :int = 0)
|
||||
{
|
||||
this.tileId = tileId;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public function hashCode () :int
|
||||
{
|
||||
return x ^ y ^ tileId;
|
||||
}
|
||||
|
||||
public function clone () :Object
|
||||
{
|
||||
var info :ObjectInfo = (ClassUtil.newInstance(this) as ObjectInfo);
|
||||
info.tileId = tileId;
|
||||
info.x = x;
|
||||
info.y = y;
|
||||
info.priority = priority;
|
||||
info.action = action;
|
||||
info.sx = sx;
|
||||
info.sy = sy;
|
||||
info.sorient = sorient;
|
||||
info.zations = zations;
|
||||
return info;
|
||||
}
|
||||
|
||||
public function equals (other :Object) :Boolean
|
||||
{
|
||||
if (other is ObjectInfo) {
|
||||
var ooi :ObjectInfo = ObjectInfo(other);
|
||||
return (x == ooi.x && y == ooi.y && tileId == ooi.tileId);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the render priority of this object tile.
|
||||
*/
|
||||
public function getPriority () :int
|
||||
{
|
||||
return priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the primary colorization assignment.
|
||||
*/
|
||||
public function getPrimaryZation () :int
|
||||
{
|
||||
return (zations & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the secondary colorization assignment.
|
||||
*/
|
||||
public function getSecondaryZation () :int
|
||||
{
|
||||
return ((zations >> 16) & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the tertiary colorization assignment.
|
||||
*/
|
||||
public function getTertiaryZation () :int
|
||||
{
|
||||
return ((zations >> 24) & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the quaternary colorization assignment.
|
||||
*/
|
||||
public function getQuaternaryZation () :int
|
||||
{
|
||||
return ((zations >> 8) & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the primary and secondary colorization assignments.
|
||||
*/
|
||||
public function setZations (primary :int, secondary :int, tertiary :int, quaternary :int) :void
|
||||
{
|
||||
zations = (primary | (secondary << 16) | (tertiary << 24) | (quaternary << 8));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this object info contains non-default data for
|
||||
* anything other than the tile id and coordinates.
|
||||
*/
|
||||
public function isInteresting () :Boolean
|
||||
{
|
||||
return (!StringUtil.isBlank(action) || priority != 0 ||
|
||||
sx != 0 || sy != 0 || zations != 0);
|
||||
}
|
||||
|
||||
/** Enhances our {@link SimpleStreamableObject#toString} output. */
|
||||
public function tileIdToString () :String
|
||||
{
|
||||
return (TileUtil.getTileSetId(tileId) + ":" +
|
||||
TileUtil.getTileIndex(tileId));
|
||||
}
|
||||
|
||||
// from interface Streamable
|
||||
override public function readObject (ins :ObjectInputStream) :void
|
||||
{
|
||||
super.readObject(ins);
|
||||
tileId = ins.readInt();
|
||||
x = ins.readInt();
|
||||
y = ins.readInt();
|
||||
priority = ins.readByte();
|
||||
action = ins.readField(String);
|
||||
sx = ins.readByte();
|
||||
sy = ins.readByte();
|
||||
sorient = ins.readByte();
|
||||
zations = ins.readInt();
|
||||
}
|
||||
|
||||
// from interface Streamable
|
||||
override public function writeObject (out :ObjectOutputStream) :void
|
||||
{
|
||||
super.writeObject(out);
|
||||
out.writeInt(tileId);
|
||||
out.writeInt(x);
|
||||
out.writeInt(y);
|
||||
out.writeByte(priority);
|
||||
out.writeField(action);
|
||||
out.writeByte(sx);
|
||||
out.writeByte(sy);
|
||||
out.writeByte(sorient);
|
||||
out.writeInt(zations);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.miso.data {
|
||||
|
||||
import flash.geom.Rectangle;
|
||||
|
||||
import com.threerings.io.ObjectInputStream;
|
||||
import com.threerings.io.SimpleStreamableObject;
|
||||
import com.threerings.io.ObjectOutputStream;
|
||||
|
||||
import com.threerings.util.ArrayIterator;
|
||||
import com.threerings.util.ClassUtil;
|
||||
import com.threerings.util.Integer;
|
||||
import com.threerings.util.Iterator;
|
||||
import com.threerings.util.Joiner;
|
||||
import com.threerings.util.MathUtil;
|
||||
import com.threerings.util.Set;
|
||||
import com.threerings.util.Sets;
|
||||
import com.threerings.util.StreamableHashMap;
|
||||
import com.threerings.util.StringUtil;
|
||||
|
||||
import com.threerings.miso.util.ObjectSet;
|
||||
import com.threerings.miso.data.MisoSceneModel;
|
||||
import com.threerings.miso.data.SparseMisoSceneModel;
|
||||
import com.threerings.miso.data.ObjectInfo;
|
||||
import com.threerings.miso.data.SparseMisoSceneModel_ObjectVisitor;
|
||||
import com.threerings.miso.data.SparseMisoSceneModel_Section;
|
||||
|
||||
/**
|
||||
* Contains miso scene data that is broken up into NxN tile sections.
|
||||
*/
|
||||
public class SparseMisoSceneModel extends MisoSceneModel
|
||||
{
|
||||
public var swidth :int;
|
||||
public var sheight :int;
|
||||
/** The dimensions of a section of our scene. */
|
||||
|
||||
/** The tileset to use when we have no tile data. */
|
||||
public var defTileSet :int = 0;
|
||||
|
||||
override public function clone () :Object
|
||||
{
|
||||
var model :SparseMisoSceneModel = (ClassUtil.newInstance(this) as SparseMisoSceneModel);
|
||||
model._sections = new StreamableHashMap();
|
||||
for (var iter :Iterator = getSections(); iter.hasNext(); ) {
|
||||
var sect :SparseMisoSceneModel_Section = SparseMisoSceneModel_Section(iter.next());
|
||||
model.setSection(SparseMisoSceneModel_Section(sect.clone()));
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
override public function getBaseTileId (col :int, row :int) :int
|
||||
{
|
||||
var sec :SparseMisoSceneModel_Section = getSection(col, row, false);
|
||||
return (sec == null) ? -1 : sec.getBaseTileId(col, row);
|
||||
}
|
||||
|
||||
override public function setBaseTile (fqBaseTileId :int, col :int, row :int) :Boolean
|
||||
{
|
||||
getSection(col, row, true).setBaseTile(col, row, fqBaseTileId);
|
||||
return true;
|
||||
}
|
||||
|
||||
override public function setDefaultBaseTileSet (tileSetId :int) :void
|
||||
{
|
||||
defTileSet = tileSetId;
|
||||
}
|
||||
|
||||
override public function getDefaultBaseTileSet () :int
|
||||
{
|
||||
return defTileSet;
|
||||
}
|
||||
|
||||
override public function getObjects (region :Rectangle, set :ObjectSet) :void
|
||||
{
|
||||
var minx :int = MathUtil.floorDiv(region.x, swidth)*swidth;
|
||||
var maxx :int = MathUtil.floorDiv(region.x+region.width-1, swidth)*swidth;
|
||||
var miny :int = MathUtil.floorDiv(region.y, sheight)*sheight;
|
||||
var maxy :int = MathUtil.floorDiv(region.y+region.height-1, sheight)*sheight;
|
||||
for (var yy :int = miny; yy <= maxy; yy += sheight) {
|
||||
for (var xx :int = minx; xx <= maxx; xx += swidth) {
|
||||
var sec :SparseMisoSceneModel_Section = getSection(xx, yy, false);
|
||||
if (sec != null) {
|
||||
sec.getObjects(region, set);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override public function addObject (info :ObjectInfo) :Boolean
|
||||
{
|
||||
return getSection(info.x, info.y, true).addObject(info);
|
||||
}
|
||||
|
||||
override public function updateObject (info :ObjectInfo) :void
|
||||
{
|
||||
// not efficient, but this is only done in editing situations
|
||||
removeObject(info);
|
||||
addObject(info);
|
||||
}
|
||||
|
||||
override public function removeObject (info :ObjectInfo) :Boolean
|
||||
{
|
||||
var sec :SparseMisoSceneModel_Section = getSection(info.x, info.y, false);
|
||||
if (sec != null) {
|
||||
return sec.removeObject(info);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all interesting {@link ObjectInfo} records in this scene to
|
||||
* the supplied list.
|
||||
*/
|
||||
public function getInterestingObjects (list :Array) :void
|
||||
{
|
||||
for (var iter :Iterator = getSections(); iter.hasNext(); ) {
|
||||
var sect :SparseMisoSceneModel_Section = SparseMisoSceneModel_Section(iter.next());
|
||||
for each (var element :ObjectInfo in sect.objectInfo) {
|
||||
list.add(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't call this method! This is only public so that the scene
|
||||
* writer can generate XML from the raw scene data.
|
||||
*/
|
||||
public function getSections () :Iterator
|
||||
{
|
||||
return new ArrayIterator(_sections.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all {@link ObjectInfo} records in this scene to the supplied list.
|
||||
*/
|
||||
public function getAllObjects (list :Array) :void
|
||||
{
|
||||
for (var iter :Iterator = getSections(); iter.hasNext(); ) {
|
||||
iter.next().getAllObjects(list);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Informs the supplied visitor of each object in this scene.
|
||||
*
|
||||
* @param interestingOnly if true, only the interesting objects will
|
||||
* be visited.
|
||||
*/
|
||||
public function visitObjects (visitor :SparseMisoSceneModel_ObjectVisitor,
|
||||
interestingOnly :Boolean = false) :void
|
||||
{
|
||||
for (var iter :Iterator = getSections(); iter.hasNext(); ) {
|
||||
var sect :SparseMisoSceneModel_Section = SparseMisoSceneModel_Section(iter.next());
|
||||
for each (var oinfo :ObjectInfo in sect.objectInfo) {
|
||||
visitor.visit(oinfo);
|
||||
}
|
||||
if (!interestingOnly) {
|
||||
for (var oo :int = 0; oo < sect.objectTileIds.length; oo++) {
|
||||
var info :ObjectInfo = new ObjectInfo(sect.objectTileIds[oo],
|
||||
sect.objectXs[oo], sect.objectYs[oo]);
|
||||
visitor.visit(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't call this method! This is only public so that the scene
|
||||
* parser can construct a scene from raw data. If only Java supported
|
||||
* class friendship.
|
||||
*/
|
||||
public function setSection (section :SparseMisoSceneModel_Section) :void
|
||||
{
|
||||
_sections.put(key(section.x, section.y), section);
|
||||
}
|
||||
|
||||
override public function getAllTilesets () :Set
|
||||
{
|
||||
var tilesets :Set = Sets.newSetOf(int);
|
||||
for each (var section :SparseMisoSceneModel_Section in _sections.values()) {
|
||||
section.getAllTilesets(tilesets);
|
||||
}
|
||||
|
||||
tilesets.add(getDefaultBaseTileSet());
|
||||
|
||||
return tilesets;
|
||||
}
|
||||
|
||||
// from interface Streamable
|
||||
override public function readObject (ins :ObjectInputStream) :void
|
||||
{
|
||||
super.readObject(ins);
|
||||
swidth = ins.readShort();
|
||||
sheight = ins.readShort();
|
||||
defTileSet = ins.readInt();
|
||||
_sections = ins.readObject(StreamableHashMap);
|
||||
}
|
||||
|
||||
// from interface Streamable
|
||||
override public function writeObject (out :ObjectOutputStream) :void
|
||||
{
|
||||
super.writeObject(out);
|
||||
out.writeShort(swidth);
|
||||
out.writeShort(sheight);
|
||||
out.writeInt(defTileSet);
|
||||
out.writeObject(_sections);
|
||||
}
|
||||
|
||||
override protected function toStringJoiner (j :Joiner) :void
|
||||
{
|
||||
super.toStringJoiner(j);
|
||||
j.add("sections", StringUtil.toString(_sections.values()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key for the specified section.
|
||||
*/
|
||||
protected function key (x :int, y :int) :Integer
|
||||
{
|
||||
var sx :int = MathUtil.floorDiv(x, swidth);
|
||||
var sy :int = MathUtil.floorDiv(y, sheight);
|
||||
return Integer.valueOf((sx << 16) | (sy & 0xFFFF));
|
||||
}
|
||||
|
||||
/** Returns the section for the specified tile coordinate. */
|
||||
protected function getSection (x :int, y :int, create :Boolean) :SparseMisoSceneModel_Section
|
||||
{
|
||||
var key :Integer = key(x, y);
|
||||
var sect :SparseMisoSceneModel_Section = _sections.get(key);
|
||||
if (sect == null && create) {
|
||||
var sx :int = MathUtil.floorDiv(x, swidth)*swidth;
|
||||
var sy :int = MathUtil.floorDiv(y, sheight)*sheight;
|
||||
_sections.put(key, sect = new SparseMisoSceneModel_Section(sx, sy, swidth, sheight));
|
||||
}
|
||||
return sect;
|
||||
}
|
||||
|
||||
/** Contains our sections in row major order. */
|
||||
protected var _sections :StreamableHashMap = new StreamableHashMap();
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.miso.data {
|
||||
|
||||
public interface SparseMisoSceneModel_ObjectVisitor
|
||||
{
|
||||
/** Called for each object in the scene, interesting and not. */
|
||||
function visit (info :ObjectInfo) :void;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.miso.data {
|
||||
|
||||
import flash.geom.Rectangle;
|
||||
|
||||
import com.threerings.io.ObjectOutputStream;
|
||||
import com.threerings.io.ObjectInputStream;
|
||||
import com.threerings.io.SimpleStreamableObject;
|
||||
import com.threerings.io.TypedArray;
|
||||
import com.threerings.media.tile.TileUtil;
|
||||
import com.threerings.miso.util.ObjectSet;
|
||||
import com.threerings.util.Arrays;
|
||||
import com.threerings.util.ClassUtil;
|
||||
import com.threerings.util.Cloneable;
|
||||
import com.threerings.util.Log;
|
||||
import com.threerings.util.Set;
|
||||
import com.threerings.util.StringUtil;
|
||||
|
||||
public class SparseMisoSceneModel_Section extends SimpleStreamableObject
|
||||
implements Cloneable
|
||||
{
|
||||
/** The tile coordinate of our upper leftmost tile. */
|
||||
public var x :int;
|
||||
public var y :int;
|
||||
|
||||
/** The width of this section in tiles. */
|
||||
public var width :int;
|
||||
|
||||
/** The combined tile ids (tile set id and tile id) for our
|
||||
* section (in row major order). */
|
||||
public var baseTileIds :TypedArray;
|
||||
|
||||
/** The combined tile ids (tile set id and tile id) of the
|
||||
* "uninteresting" tiles in the object layer. */
|
||||
public var objectTileIds :TypedArray = TypedArray.create(int);
|
||||
|
||||
/** The x coordinate of the "uninteresting" tiles in the object
|
||||
* layer. */
|
||||
public var objectXs :TypedArray = TypedArray.createShort();
|
||||
|
||||
/** The y coordinate of the "uninteresting" tiles in the object
|
||||
* layer. */
|
||||
public var objectYs :TypedArray = TypedArray.createShort();
|
||||
|
||||
/** Information records for the "interesting" objects in the
|
||||
* object layer. */
|
||||
public var objectInfo :TypedArray = TypedArray.create(ObjectInfo);
|
||||
|
||||
/**
|
||||
* Creates a new scene section with the specified dimensions.
|
||||
*/
|
||||
public function SparseMisoSceneModel_Section (
|
||||
x :int = 0, y :int = 0, width :int = 0, height :int = 0)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.width = width;
|
||||
baseTileIds = TypedArray.create(int, Arrays.create(width*height));
|
||||
}
|
||||
|
||||
public function getBaseTileId (col :int, row :int) :int
|
||||
{
|
||||
if (col < x || col >= (x+width) || row < y || row >= (y+width)) {
|
||||
log.warning("Requested bogus tile +" + col + "+" + row +
|
||||
" from " + this + ".");
|
||||
return -1;
|
||||
} else {
|
||||
return baseTileIds[(row-y)*width+(col-x)];
|
||||
}
|
||||
}
|
||||
|
||||
public function setBaseTile (col :int, row :int, fqBaseTileId :int) :void
|
||||
{
|
||||
baseTileIds[(row-y)*width+(col-x)] = fqBaseTileId;
|
||||
}
|
||||
|
||||
public function addObject (info :ObjectInfo) :Boolean
|
||||
{
|
||||
// sanity check: see if there is already an object of this
|
||||
// type at these coordinates
|
||||
var dupidx :int;
|
||||
if ((dupidx = Arrays.indexOf(objectInfo, info)) != -1) {
|
||||
log.warning("Refusing to add duplicate object [ninfo=" + info +
|
||||
", oinfo=" + objectInfo[dupidx] + "].");
|
||||
return false;
|
||||
}
|
||||
if ((dupidx = indexOfUn(info)) != -1) {
|
||||
log.warning("Refusing to add duplicate object " +
|
||||
"[info=" + info + "].");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (info.isInteresting()) {
|
||||
objectInfo = TypedArray(objectInfo.concat(info));
|
||||
} else {
|
||||
objectTileIds = TypedArray(objectTileIds.concat(info.tileId));
|
||||
objectXs = TypedArray(objectXs.concat(info.x));
|
||||
objectYs = TypedArray(objectYs.concat(info.y));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function removeObject (info :ObjectInfo) :Boolean
|
||||
{
|
||||
// look for it in the interesting info array
|
||||
var oidx :int = Arrays.indexOf(objectInfo, info);
|
||||
if (oidx != -1) {
|
||||
objectInfo = TypedArray(Arrays.splice(objectInfo, oidx, 1));
|
||||
return true;
|
||||
}
|
||||
|
||||
// look for it in the uninteresting arrays
|
||||
oidx = indexOfUn(info);
|
||||
if (oidx != -1) {
|
||||
objectTileIds = TypedArray(Arrays.splice(objectTileIds, oidx, 1));
|
||||
objectXs = TypedArray(Arrays.splice(objectXs, oidx, 1));
|
||||
objectYs = TypedArray(Arrays.splice(objectYs, oidx, 1));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the specified object in the uninteresting
|
||||
* arrays or -1 if it is not in this section as an uninteresting
|
||||
* object.
|
||||
*/
|
||||
protected function indexOfUn (info :ObjectInfo) :int
|
||||
{
|
||||
for (var ii :int = 0; ii < objectTileIds.length; ii++) {
|
||||
if (objectTileIds[ii] == info.tileId &&
|
||||
objectXs[ii] == info.x && objectYs[ii] == info.y) {
|
||||
return ii;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public function getAllObjects (list :Array) :void
|
||||
{
|
||||
for each (var info :ObjectInfo in objectInfo) {
|
||||
list.add(info);
|
||||
}
|
||||
for (var ii :int= 0; ii < objectTileIds.length; ii++) {
|
||||
var x :int = objectXs[ii];
|
||||
var y :int = objectYs[ii];
|
||||
list.add(new ObjectInfo(objectTileIds[ii], x, y));
|
||||
}
|
||||
}
|
||||
|
||||
public function getObjects (region :Rectangle, set :ObjectSet) :void
|
||||
{
|
||||
// first look for intersecting interesting objects
|
||||
for each (var info :ObjectInfo in objectInfo) {
|
||||
if (region.contains(info.x, info.y)) {
|
||||
set.insert(info);
|
||||
}
|
||||
}
|
||||
|
||||
// now look for intersecting non-interesting objects
|
||||
for (var ii :int = 0; ii < objectTileIds.length; ii++) {
|
||||
var x :int = objectXs[ii];
|
||||
var y :int = objectYs[ii];
|
||||
if (region.contains(x, y)) {
|
||||
set.insert(new ObjectInfo(objectTileIds[ii], x, y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this section contains no data beyond the default.
|
||||
* Used when saving a sparse scene: we omit blank sections.
|
||||
*/
|
||||
public function isBlank () :Boolean
|
||||
{
|
||||
if ((objectTileIds.length != 0) || (objectInfo.length != 0)) {
|
||||
return false;
|
||||
}
|
||||
for each (var baseTileId :int in baseTileIds) {
|
||||
if (baseTileId != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function clone () :Object
|
||||
{
|
||||
var section :SparseMisoSceneModel_Section =
|
||||
(ClassUtil.newInstance(this) as SparseMisoSceneModel_Section);
|
||||
section.x = x;
|
||||
section.y = y;
|
||||
section.width = width;
|
||||
section.baseTileIds = TypedArray(Arrays.copyOf(baseTileIds));
|
||||
section.objectTileIds = TypedArray(Arrays.copyOf(objectTileIds));
|
||||
section.objectXs = TypedArray(Arrays.copyOf(objectXs));
|
||||
section.objectYs = TypedArray(Arrays.copyOf(objectYs));
|
||||
section.objectInfo = TypedArray.create(ObjectInfo);
|
||||
for (var ii :int = 0; ii < objectInfo.length; ii++) {
|
||||
section.objectInfo[ii] = objectInfo[ii].clone();
|
||||
}
|
||||
return section;
|
||||
}
|
||||
|
||||
override public function toString () :String
|
||||
{
|
||||
if (width == 0 || baseTileIds == null) {
|
||||
return "<no bounds>";
|
||||
} else {
|
||||
return width + "x" + (baseTileIds.length / width) + "+" + x + ":" + y + ":" +
|
||||
objectInfo.length;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all tilesets we reference to the set.
|
||||
*/
|
||||
public function getAllTilesets (tilesets :Set) :void
|
||||
{
|
||||
for each (var base :int in baseTileIds) {
|
||||
var baseSetId :int = TileUtil.getTileSetId(base);
|
||||
if (baseSetId != 0) {
|
||||
tilesets.add(baseSetId);
|
||||
}
|
||||
}
|
||||
for each (var obj :int in objectTileIds) {
|
||||
tilesets.add(TileUtil.getTileSetId(obj));
|
||||
}
|
||||
|
||||
for each (var objInfo :ObjectInfo in objectInfo) {
|
||||
tilesets.add(TileUtil.getTileSetId(objInfo.tileId));
|
||||
}
|
||||
}
|
||||
|
||||
// from interface Streamable
|
||||
override public function readObject (ins :ObjectInputStream) :void
|
||||
{
|
||||
super.readObject(ins);
|
||||
x = ins.readShort();
|
||||
y = ins.readShort();
|
||||
width = ins.readInt();
|
||||
baseTileIds = ins.readField(TypedArray.getJavaType(int));
|
||||
objectTileIds = ins.readField(TypedArray.getJavaType(int));
|
||||
objectXs = ins.readField(TypedArray.getJavaShortType());
|
||||
objectYs = ins.readField(TypedArray.getJavaShortType());
|
||||
objectInfo = TypedArray(ins.readObject());
|
||||
}
|
||||
|
||||
// from interface Streamable
|
||||
override public function writeObject (out :ObjectOutputStream) :void
|
||||
{
|
||||
super.writeObject(out);
|
||||
out.writeShort(x);
|
||||
out.writeShort(y);
|
||||
out.writeInt(width);
|
||||
out.writeField(baseTileIds);
|
||||
out.writeField(objectTileIds);
|
||||
out.writeField(objectXs);
|
||||
out.writeField(objectYs);
|
||||
out.writeField(objectInfo);
|
||||
}
|
||||
|
||||
private static const log :Log = Log.getLog(SparseMisoSceneModel_Section);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.miso.data {
|
||||
|
||||
import com.threerings.io.ObjectInputStream;
|
||||
import com.threerings.miso.data.ObjectInfo;
|
||||
import com.threerings.io.ObjectOutputStream;
|
||||
|
||||
/**
|
||||
* A convenient base class for "virtual" scenes which do not allow editing
|
||||
* and compute the base and object tiles rather than obtain them from some
|
||||
* data structure.
|
||||
*/
|
||||
public /*abstract*/ class VirtualMisoSceneModel extends MisoSceneModel
|
||||
{
|
||||
override public function setBaseTile (arg1 :int, arg2 :int, arg3 :int) :Boolean
|
||||
{
|
||||
throw new Error("Unsupported");
|
||||
}
|
||||
|
||||
override public function addObject (arg1 :ObjectInfo) :Boolean
|
||||
{
|
||||
throw new Error("Unsupported");
|
||||
}
|
||||
|
||||
override public function updateObject (arg1 :ObjectInfo) :void
|
||||
{
|
||||
throw new Error("Unsupported");
|
||||
}
|
||||
|
||||
override public function removeObject (arg1 :ObjectInfo) :Boolean
|
||||
{
|
||||
throw new Error("Unsupported");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,474 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.miso.tile {
|
||||
|
||||
import flash.geom.Point;
|
||||
import flash.geom.Rectangle;
|
||||
|
||||
import flash.display.Bitmap;
|
||||
import flash.display.BitmapData;
|
||||
|
||||
import com.threerings.display.ImageUtil;
|
||||
import com.threerings.media.tile.NoSuchTileSetError;
|
||||
import com.threerings.media.tile.BaseTile;
|
||||
import com.threerings.media.tile.Tile;
|
||||
import com.threerings.media.tile.TileSet;
|
||||
import com.threerings.media.tile.TileManager;
|
||||
import com.threerings.media.tile.TileUtil;
|
||||
import com.threerings.util.Arrays;
|
||||
import com.threerings.util.Integer;
|
||||
import com.threerings.util.Log;
|
||||
import com.threerings.util.Maps;
|
||||
import com.threerings.util.Map;
|
||||
import com.threerings.util.Sets;
|
||||
import com.threerings.util.Set;
|
||||
import com.threerings.util.StringUtil;
|
||||
import com.threerings.util.WeakReference;
|
||||
import com.threerings.miso.data.MisoSceneModel;
|
||||
|
||||
public class AutoFringer
|
||||
{
|
||||
private var log :Log = Log.getLog(AutoFringer);
|
||||
|
||||
public function AutoFringer (fringeConf :FringeConfiguration, tMgr :TileManager)
|
||||
{
|
||||
_fringeConf = fringeConf;
|
||||
_tMgr = tMgr;
|
||||
|
||||
|
||||
// Construct the BITS_TO_INDEX array.
|
||||
var initIdx :int;
|
||||
// first clear everything to -1 (meaning there is no tile defined)
|
||||
for (initIdx = 0; initIdx < INIT_CT; initIdx++) {
|
||||
BITS_TO_INDEX[initIdx] = -1;
|
||||
}
|
||||
|
||||
// then fill in with the defined tiles.
|
||||
for (initIdx = 0; initIdx < FRINGETILES.length; initIdx++) {
|
||||
BITS_TO_INDEX[FRINGETILES[initIdx]] = initIdx;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the fringe configuration used by this fringer.
|
||||
*/
|
||||
public function getFringeConf () :FringeConfiguration
|
||||
{
|
||||
return _fringeConf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the tilesets that fringe on the specified tilesets.
|
||||
*/
|
||||
public function getFringeSets (tilesets :Set) :Set
|
||||
{
|
||||
var fringeSets :Set = Sets.newSetOf(int);
|
||||
|
||||
tilesets.forEach(function(o :int) :void {
|
||||
var fringes :Array = _fringeConf.getFringeSets(o);
|
||||
if (fringes != null) {
|
||||
for each (var fringe :int in fringes) {
|
||||
fringeSets.add(fringe);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return fringeSets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute and return the fringe tile to be inserted at the specified location.
|
||||
*/
|
||||
public function getFringeTile (scene :MisoSceneModel, col :int, row :int, fringes :Map,
|
||||
masks :Map) :BaseTile
|
||||
{
|
||||
// get the tileset id of the base tile we are considering
|
||||
var utid :int = scene.getBaseTileId(col, row);
|
||||
var underset :int = adjustTileSetId((utid <= 0) ?
|
||||
scene.getDefaultBaseTileSet() : (utid >> 16));
|
||||
|
||||
// start with a clean temporary fringer map
|
||||
_fringers.clear();
|
||||
var passable :Boolean = true;
|
||||
|
||||
// walk through our influence tiles
|
||||
for (var y :int = row - 1, maxy :int = row + 2; y < maxy; y++) {
|
||||
for (var x :int = col - 1, maxx :int = col + 2; x < maxx; x++) {
|
||||
// we sensibly do not consider ourselves
|
||||
if ((x == col) && (y == row)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// determine the tileset for this tile
|
||||
var btid :int= scene.getBaseTileId(x, y);
|
||||
var baseset :int= adjustTileSetId((btid <= 0) ?
|
||||
scene.getDefaultBaseTileSet() : (btid >> 16));
|
||||
|
||||
// determine if it fringes on our tile
|
||||
var pri :int = _fringeConf.fringesOn(baseset, underset);
|
||||
if (pri == -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var fringer :FringerRec = FringerRec(_fringers.get(baseset));
|
||||
if (fringer == null) {
|
||||
fringer = new FringerRec(baseset, pri);
|
||||
_fringers.put(baseset, fringer);
|
||||
}
|
||||
|
||||
// now turn on the appropriate fringebits
|
||||
fringer.bits |= FLAGMATRIX[y - row + 1][x - col + 1];
|
||||
|
||||
// See if a tile that fringes on us kills our passability,
|
||||
// but don't count the default base tile against us, as
|
||||
// we allow users to splash in the water.
|
||||
if (passable && (btid > 0)) {
|
||||
try {
|
||||
var bt :BaseTile = BaseTile(_tMgr.getTile(btid));
|
||||
passable = bt.isPassable();
|
||||
} catch (nstse :NoSuchTileSetError) {
|
||||
log.warning("Autofringer couldn't find a base set while attempting to " +
|
||||
"figure passability", nstse);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if nothing fringed, we're done
|
||||
var numfringers :int = _fringers.size();
|
||||
if (numfringers == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// otherwise compose a FringeTile from the specified fringes
|
||||
var frecs :Array = _fringers.values();
|
||||
|
||||
return composeFringeTile(frecs, fringes, TileUtil.getTileHash(col, row), passable, masks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose a FringeTile out of the various fringe images needed.
|
||||
*/
|
||||
protected function composeFringeTile (fringers :Array,
|
||||
fringes :Map, hashValue :int, passable :Boolean, masks :Map) :FringeTile
|
||||
{
|
||||
// sort the array so that higher priority fringers get drawn first
|
||||
Arrays.sort(fringers);
|
||||
|
||||
// Generate an identifier for the fringe tile being created as an array of the keys of its
|
||||
// component tiles in the order they'll be drawn in the fringe tile.
|
||||
var fringeIds :Array = [];
|
||||
for each (var fringer :FringerRec in fringers) {
|
||||
var indexes :Array = getFringeIndexes(fringer.bits);
|
||||
var tsr :FringeTileSetRecord = _fringeConf.getFringe(fringer.baseset, hashValue);
|
||||
var fringeset :int = tsr.fringe_tsid;
|
||||
for each (var index :int in indexes) {
|
||||
// Add a key for this tile as an int containing its base tile, the fringe set it's
|
||||
// working with and the index used in that set.
|
||||
fringeIds.push((fringer.baseset << 20) + (fringeset << 8) + index);
|
||||
}
|
||||
}
|
||||
var frTile :FringeTile = new FringeTile(fringeIds, passable);
|
||||
|
||||
// If the fringes map contains something with the same fringe identifier, this will pull
|
||||
// it out and we can use it instead.
|
||||
var result :WeakReference = fringes.get(frTile);
|
||||
if (result != null) {
|
||||
var fringe :FringeTile = result.get();
|
||||
if (fringe != null) {
|
||||
return fringe;
|
||||
}
|
||||
}
|
||||
|
||||
// There's no fringe with the same identifier, so we need to create the tile.
|
||||
var img :Bitmap = null;
|
||||
getTileImageHelper1(img, hashValue, masks, fringers, 0, function (result :Bitmap) :void {
|
||||
frTile.setImage(result);
|
||||
});
|
||||
fringes.put(frTile, new WeakReference(frTile));
|
||||
return frTile;
|
||||
}
|
||||
|
||||
protected function getTileImageHelper1 (img :Bitmap, hashValue :int, masks :Map,
|
||||
fringers :Array, fringerIdx :int, callback :Function) :void
|
||||
{
|
||||
var fringer :FringerRec = fringers[fringerIdx];
|
||||
var indexes :Array = getFringeIndexes(fringer.bits);
|
||||
var tsr :FringeTileSetRecord = _fringeConf.getFringe(fringer.baseset, hashValue);
|
||||
getTileImageHelper0(img, tsr, fringer.baseset, hashValue, masks, indexes, 0,
|
||||
function (result :Bitmap) :void {
|
||||
if (fringerIdx == fringers.length - 1) {
|
||||
callback(result);
|
||||
} else {
|
||||
getTileImageHelper1(result, hashValue, masks, fringers, fringerIdx + 1,
|
||||
callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected function getTileImageHelper0 (img :Bitmap, tsr :FringeTileSetRecord,
|
||||
baseset :int, hashValue :int, masks :Map, indexes :Array,
|
||||
indexIdx :int, callback :Function) :void
|
||||
{
|
||||
try {
|
||||
getTileImage(img, tsr, baseset, indexes[indexIdx], hashValue, masks,
|
||||
function (result :Bitmap) :void {
|
||||
if (indexIdx == indexes.length - 1) {
|
||||
callback(result);
|
||||
} else {
|
||||
getTileImageHelper0(result, tsr, baseset, hashValue, masks, indexes,
|
||||
indexIdx + 1, callback);
|
||||
}
|
||||
});
|
||||
} catch (nstse :NoSuchTileSetError) {
|
||||
log.warning("Autofringer couldn't find a needed tileset", nstse);
|
||||
callback(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve or compose an image for the specified fringe.
|
||||
*/
|
||||
protected function getTileImage (img :Bitmap, tsr :FringeTileSetRecord ,
|
||||
baseset :int, index :int, hashValue :int, masks :Map, callback :Function) :void
|
||||
{
|
||||
|
||||
var fringeset :int = tsr.fringe_tsid;
|
||||
var fset :TileSet = _tMgr.getTileSet(fringeset);
|
||||
if (!tsr.mask) {
|
||||
// oh good, this is easy
|
||||
var stamp :Tile = fset.getTile(index);
|
||||
if (stamp.getImage() != null) {
|
||||
callback(stampTileImage(stamp, img, stamp.getWidth(), stamp.getHeight()));
|
||||
} else {
|
||||
stamp.notifyOnLoad(function(tile :Tile) :void {
|
||||
callback(stampTileImage(stamp, img, stamp.getWidth(), stamp.getHeight()));
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// otherwise, it's a mask..
|
||||
var maskkey :int = (baseset << 20) + (fringeset << 8) + index;
|
||||
var mask :Bitmap = masks.get(maskkey);
|
||||
if (mask == null) {
|
||||
_tMgr.getTileSet(fringeset).getTileImage(index, null, function (fsrc :Bitmap) :void {
|
||||
_tMgr.getTileSet(baseset).getTileImage(0, null, function (bsrc :Bitmap) :void {
|
||||
mask = composeMaskedImage(fsrc, bsrc);
|
||||
masks.put(maskkey, mask);
|
||||
callback(stampTileImage(mask, img, mask.width, mask.height));
|
||||
});
|
||||
});
|
||||
|
||||
} else {
|
||||
callback(stampTileImage(mask, img, mask.width, mask.height));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Create an image using the alpha channel from the first and the RGB values from the second.
|
||||
*/
|
||||
public static function composeMaskedImage (mask :Bitmap, base :Bitmap) :Bitmap
|
||||
{
|
||||
var data :BitmapData = new BitmapData(base.width, base.height);
|
||||
data.copyPixels(base.bitmapData, new Rectangle(0, 0, base.width, base.height),
|
||||
new Point(0, 0), mask.bitmapData, new Point(0, 0), false);
|
||||
return new Bitmap(data);
|
||||
}
|
||||
|
||||
/** Helper function for {@link #getTileImage}. */
|
||||
protected function stampTileImage (stamp :Object, ftimg :Bitmap, width :int,
|
||||
height :int) :Bitmap
|
||||
{
|
||||
// create the target image if necessary
|
||||
if (ftimg == null) {
|
||||
ftimg = new Bitmap(new BitmapData(width, height, true, 0x00000000));
|
||||
}
|
||||
var img :Bitmap;
|
||||
if (stamp is Tile) {
|
||||
img = Bitmap((Tile(stamp)).getImage());
|
||||
} else {
|
||||
img = Bitmap(stamp);
|
||||
}
|
||||
|
||||
ftimg.bitmapData.draw(img);
|
||||
|
||||
return ftimg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fringe index specified by the fringebits. If no index is available, try breaking
|
||||
* down the bits into contiguous regions of bits and look for indexes for those.
|
||||
*/
|
||||
protected function getFringeIndexes (bits :int) :Array
|
||||
{
|
||||
var index :int = BITS_TO_INDEX[bits];
|
||||
if (index != -1) {
|
||||
return [index];
|
||||
}
|
||||
|
||||
// otherwise, split the bits into contiguous components
|
||||
|
||||
// look for a zero and start our first split
|
||||
var start :int = 0;
|
||||
while ((((1 << start) & bits) != 0) && (start < NUM_FRINGEBITS)) {
|
||||
start++;
|
||||
}
|
||||
|
||||
if (start == NUM_FRINGEBITS) {
|
||||
// we never found an empty fringebit, and since index (above)
|
||||
// was already -1, we have no fringe tile for these bits.. sad.
|
||||
return new Array(0);
|
||||
}
|
||||
|
||||
var indexes :Array = [];
|
||||
var weebits :int = 0;
|
||||
for (var ii :int = (start + 1) % NUM_FRINGEBITS; ii != start;
|
||||
ii = (ii + 1) % NUM_FRINGEBITS) {
|
||||
|
||||
if (((1 << ii) & bits) != 0) {
|
||||
weebits |= (1 << ii);
|
||||
} else if (weebits != 0) {
|
||||
index = BITS_TO_INDEX[weebits];
|
||||
if (index != -1) {
|
||||
indexes.push(index);
|
||||
}
|
||||
weebits = 0;
|
||||
}
|
||||
}
|
||||
if (weebits != 0) {
|
||||
index = BITS_TO_INDEX[weebits];
|
||||
if (index != -1) {
|
||||
indexes.push(index);
|
||||
}
|
||||
}
|
||||
|
||||
return indexes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow subclasses to apply arbitrary modifications to tileset ids for whatever nefarious
|
||||
* purposes they may have.
|
||||
*/
|
||||
protected function adjustTileSetId (tileSetId :int) :int
|
||||
{
|
||||
// by default, nothing.
|
||||
return tileSetId;
|
||||
}
|
||||
|
||||
// fringe bits
|
||||
// see docs/miso/fringebits.png
|
||||
//
|
||||
protected static const NORTH :int = 1 << 0;
|
||||
protected static const NORTHEAST :int = 1 << 1;
|
||||
protected static const EAST :int = 1 << 2;
|
||||
protected static const SOUTHEAST :int = 1 << 3;
|
||||
protected static const SOUTH :int = 1 << 4;
|
||||
protected static const SOUTHWEST :int = 1 << 5;
|
||||
protected static const WEST :int = 1 << 6;
|
||||
protected static const NORTHWEST :int = 1 << 7;
|
||||
|
||||
protected static const NUM_FRINGEBITS :int = 8;
|
||||
|
||||
// A matrix mapping adjacent tiles to which fringe bits they affect.
|
||||
// (x and y are offset by +1, since we can't have -1 as an array index)
|
||||
// again, see docs/miso/fringebits.png
|
||||
//
|
||||
protected static const FLAGMATRIX :Array= [
|
||||
[ NORTHEAST, (NORTHEAST | EAST | SOUTHEAST), SOUTHEAST ],
|
||||
[ (NORTHWEST | NORTH | NORTHEAST), 0, (SOUTHEAST | SOUTH | SOUTHWEST) ],
|
||||
[ NORTHWEST, (NORTHWEST | WEST | SOUTHWEST), SOUTHWEST ]
|
||||
];
|
||||
|
||||
/**
|
||||
* The fringe tiles we use. These are the 17 possible tiles made up of continuous fringebits
|
||||
* sections. Huh? see docs/miso/fringebits.png
|
||||
*/
|
||||
protected static const FRINGETILES :Array = [
|
||||
SOUTHEAST,
|
||||
SOUTHWEST | SOUTH | SOUTHEAST,
|
||||
SOUTHWEST,
|
||||
NORTHEAST | EAST | SOUTHEAST,
|
||||
NORTHWEST | WEST | SOUTHWEST,
|
||||
NORTHEAST,
|
||||
NORTHWEST | NORTH | NORTHEAST,
|
||||
NORTHWEST,
|
||||
|
||||
SOUTHWEST | WEST | NORTHWEST | NORTH | NORTHEAST,
|
||||
NORTHWEST | NORTH | NORTHEAST | EAST | SOUTHEAST,
|
||||
NORTHWEST | WEST | SOUTHWEST | SOUTH | SOUTHEAST,
|
||||
SOUTHWEST | SOUTH | SOUTHEAST | EAST | NORTHEAST,
|
||||
|
||||
NORTHEAST | NORTH | NORTHWEST | WEST | SOUTHWEST | SOUTH | SOUTHEAST,
|
||||
SOUTHEAST | EAST | NORTHEAST | NORTH | NORTHWEST | WEST | SOUTHWEST,
|
||||
SOUTHWEST | SOUTH | SOUTHEAST | EAST | NORTHEAST | NORTH | NORTHWEST,
|
||||
NORTHWEST | WEST | SOUTHWEST | SOUTH | SOUTHEAST | EAST | NORTHEAST,
|
||||
|
||||
// all the directions!
|
||||
NORTH | NORTHEAST | EAST | SOUTHEAST | SOUTH | SOUTHWEST | WEST | NORTHWEST
|
||||
];
|
||||
|
||||
// A reverse map of the above array, for quickly looking up which tile
|
||||
// we want.
|
||||
protected const INIT_CT :int = (1 << NUM_FRINGEBITS);
|
||||
|
||||
protected const BITS_TO_INDEX :Array = new Array(INIT_CT);
|
||||
|
||||
protected var _tMgr :TileManager;
|
||||
protected var _fringeConf :FringeConfiguration;
|
||||
protected var _fringers :Map = Maps.newMapOf(int);
|
||||
}
|
||||
}
|
||||
|
||||
import com.threerings.util.Comparable;
|
||||
|
||||
/**
|
||||
* A record for holding information about a particular fringe as we're computing what it will
|
||||
* look like.
|
||||
*/
|
||||
class FringerRec
|
||||
implements Comparable
|
||||
{
|
||||
public var baseset :int;
|
||||
public var priority :int;
|
||||
public var bits :int;
|
||||
|
||||
public function FringerRec (base :int, pri :int)
|
||||
{
|
||||
baseset = base;
|
||||
priority = pri;
|
||||
}
|
||||
|
||||
public function compareTo (o :Object) :int
|
||||
{
|
||||
return priority - FringerRec(o).priority;
|
||||
}
|
||||
|
||||
public function toString () :String
|
||||
{
|
||||
return "[base=" + baseset + ", pri=" + priority + ", bits="
|
||||
+ bits.toString(16) + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.miso.tile {
|
||||
|
||||
import com.threerings.util.Map;
|
||||
import com.threerings.util.Maps;
|
||||
import com.threerings.media.tile.TileSetIdMap;
|
||||
|
||||
/**
|
||||
* Used to manage data about which base tilesets fringe on which others
|
||||
* and how they fringe.
|
||||
*/
|
||||
public class FringeConfiguration
|
||||
{
|
||||
public static function fromXml (xml :XML, idMap :TileSetIdMap) :FringeConfiguration
|
||||
{
|
||||
var config :FringeConfiguration = new FringeConfiguration();
|
||||
for each (var baseXml :XML in xml.base) {
|
||||
config.addFringeRecord(FringeRecord.fromXml(baseXml, idMap));
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a parsed FringeRecord to this instance. This is used when parsing
|
||||
* the fringerecords from xml.
|
||||
*/
|
||||
public function addFringeRecord (frec :FringeRecord) :void
|
||||
{
|
||||
_frecs.put(frec.base_tsid, frec);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the first base tileset fringes upon the second, return the
|
||||
* fringe priority of the first base tileset, otherwise return -1.
|
||||
*/
|
||||
public function fringesOn (first :int, second :int) :int
|
||||
{
|
||||
// Short-circuit if we're fringing on ourselves.
|
||||
if (first == second) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
var f1 :FringeRecord = _frecs.get(first);
|
||||
|
||||
// we better have a fringe record for the first
|
||||
if (null != f1) {
|
||||
|
||||
// it had better have some tilesets defined
|
||||
if (f1.tilesets.length > 0) {
|
||||
|
||||
var f2 :FringeRecord = _frecs.get(second);
|
||||
|
||||
// and we only fringe if second doesn't exist or has a lower
|
||||
// priority
|
||||
if ((null == f2) || (f1.priority > f2.priority)) {
|
||||
return f1.priority;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a random FringeTileSetRecord from amongst the ones
|
||||
* listed for the specified base tileset.
|
||||
*/
|
||||
public function getFringe (baseset :int, hashValue :int) :FringeTileSetRecord
|
||||
{
|
||||
var f :FringeRecord = _frecs.get(baseset);
|
||||
return f.tilesets[hashValue % f.tilesets.length];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the tilesets used to fringe with this baseset.
|
||||
*/
|
||||
public function getFringeSets (baseset :int) :Array
|
||||
{
|
||||
var f :FringeRecord = _frecs.get(baseset);
|
||||
if (f == null) {
|
||||
return [];
|
||||
} else {
|
||||
return f.tilesets.map(
|
||||
function (element :FringeTileSetRecord, index:int, arr:Array) :int {
|
||||
return element.fringe_tsid;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** The mapping from base tileset id to fringerecord. */
|
||||
protected var _frecs :Map = Maps.newMapOf(int);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.miso.tile {
|
||||
|
||||
import com.threerings.media.tile.TileSetIdMap;
|
||||
import com.threerings.util.StringUtil;
|
||||
|
||||
public class FringeRecord
|
||||
{
|
||||
/** The tileset id of the base tileset to which this applies. */
|
||||
public var base_tsid :int;
|
||||
|
||||
/** The fringe priority of this base tileset. */
|
||||
public var priority :int;
|
||||
|
||||
/** A list of the possible tilesets that can be used for fringing. */
|
||||
public var tilesets :Array = [];
|
||||
|
||||
public static function fromXml (xml :XML, idMap :TileSetIdMap) :FringeRecord
|
||||
{
|
||||
var rec :FringeRecord = new FringeRecord();
|
||||
rec.base_tsid = idMap.getTileSetId(xml.@name);
|
||||
rec.priority = xml.@priority;
|
||||
for each (var tsXml :XML in xml.tileset) {
|
||||
rec.addTileset(FringeTileSetRecord.fromXml(tsXml, idMap));
|
||||
}
|
||||
return rec;
|
||||
}
|
||||
|
||||
/** Used when parsing the tilesets definitions. */
|
||||
public function addTileset (record :FringeTileSetRecord) :void
|
||||
{
|
||||
tilesets.push(record);
|
||||
}
|
||||
|
||||
/** Did everything parse well? */
|
||||
public function isValid () :Boolean
|
||||
{
|
||||
return ((base_tsid != 0) && (priority > 0));
|
||||
}
|
||||
|
||||
public function toString () :String
|
||||
{
|
||||
return "[base_tsid=" + base_tsid + ", priority=" + priority +
|
||||
", tilesets=" + StringUtil.toString(tilesets) + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.miso.tile {
|
||||
|
||||
import com.threerings.util.Arrays;
|
||||
import com.threerings.util.Hashable;
|
||||
|
||||
import com.threerings.media.tile.BaseTile;
|
||||
|
||||
public class FringeTile extends BaseTile
|
||||
implements Hashable
|
||||
{
|
||||
public function FringeTile (fringeIds :Array, passable :Boolean)
|
||||
{
|
||||
setPassable(passable);
|
||||
_fringeIds = fringeIds;
|
||||
}
|
||||
|
||||
public function equals (obj :Object) :Boolean
|
||||
{
|
||||
if (!(obj is FringeTile)) {
|
||||
return false;
|
||||
}
|
||||
var fObj :FringeTile = FringeTile(obj);
|
||||
return _passable == fObj._passable && Arrays.equals(_fringeIds, fObj._fringeIds);
|
||||
}
|
||||
|
||||
public function hashCode () :int
|
||||
{
|
||||
var result :int = 33; // can't use Arrays.hashCode(long) as it's 1.5 only
|
||||
for each (var key :int in _fringeIds) {
|
||||
result = result * 37 + key;
|
||||
}
|
||||
if (_passable) {
|
||||
result++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** The fringe keys of the tiles that went into this tile in the order they were drawn. */
|
||||
protected var _fringeIds :Array;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.miso.tile {
|
||||
|
||||
import com.threerings.media.tile.TileSetIdMap;
|
||||
import com.threerings.util.XmlUtil;
|
||||
|
||||
public class FringeTileSetRecord
|
||||
{
|
||||
/** The tileset id of the fringe tileset. */
|
||||
public var fringe_tsid :int;
|
||||
|
||||
/** Is this a mask? */
|
||||
public var mask :Boolean;
|
||||
|
||||
public static function fromXml (xml :XML, idMap :TileSetIdMap) :FringeTileSetRecord
|
||||
{
|
||||
var rec :FringeTileSetRecord = new FringeTileSetRecord();
|
||||
rec.fringe_tsid = idMap.getTileSetId(xml.@name);
|
||||
rec.mask = XmlUtil.getBooleanAttr(xml, "mask", false);
|
||||
return rec;
|
||||
}
|
||||
|
||||
/** Did everything parse well? */
|
||||
public function isValid () :Boolean
|
||||
{
|
||||
return (fringe_tsid != 0);
|
||||
}
|
||||
|
||||
public function toString () :String
|
||||
{
|
||||
return "[fringe_tsid=" + fringe_tsid + ", mask=" + mask + "]";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user