Split Nenya into proper Maven submodules (same treatment Narya got).

This commit is contained in:
Michael Bayne
2012-02-27 11:46:22 -08:00
parent 90146d517d
commit 5e2380eb24
645 changed files with 358 additions and 283 deletions
@@ -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;
}
}