/*
 * Copyright 2008 Adobe Systems Inc., 2008 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Contributor(s):
 *   Zwetan Kjukov <zwetan@gmail.com>.
 *   Marc Alcaraz <ekameleon@gmail.com>.
 */

package com.google.analytics.core
{

    /**
     * The domain name mode enumeration class.
     */
    public class DomainNameMode
    {
        /**
         * @private
         */
        private var _value:int;

        /**
         * @private
         */
        private var _name:String;

        /**
         * Creates a new DomainNameMode instance.
         * @param value The enumeration value representation.
         * @param name The enumeration name representation.
         */
        public function DomainNameMode( value:int = 0, name:String = "" )
        {
            _value = value;
            _name  = name;
        }

        /**
         * Returns the primitive value of the object.
         * @return the primitive value of the object.
         */
        public function valueOf():int
        {
            return _value;
        }

        /**
         * Returns the String representation of the object.
         * @return the String representation of the object.
         */
        public function toString():String
        {
            return _name;
        }

        /**
         * Determinates the "none" DomainNameMode value.
         *
"none" is used in the following two situations :

         *
- You want to disable tracking across hosts.

         *
- You want to set up tracking across two separate domains.

         *
Cross- domain tracking requires configuration of the setAllowLinker() and link() methods.

         */
        public static const none:DomainNameMode    = new DomainNameMode( 0, "none" );

        /**
         * Attempts to automaticaly resolve the domain name.
         */
        public static const auto:DomainNameMode    = new DomainNameMode( 1, "auto" );

        /**
         * Custom is used to set explicitly to your domain name if your website spans multiple hostnames,
         * and you want to track visitor behavior across all hosts.
         *
For example, if you have two hosts : server1.example.com and server2.example.com,
         * you would set the domain name as follows :
         *

         * pageTracker.setDomainName( new Domain( DomainName.custom, ".example.com" ) ) ;
         *

         */
        public static const custom:DomainNameMode  = new DomainNameMode( 2, "custom" );

    }
}

fix comment for asdoc

fix comment for asdoc


package asx.fn
{
	import flash.events.EventDispatcher;
	import flash.events.EventDispatcher;
	import flash.events.Event;
	import flash.events.TimerEvent;
	import flash.utils.getTimer;
	import flash.utils.Timer;

	import org.flexunit.async.Async;
	import org.flexunit.assertThat;
	import org.hamcrest.object.equalTo;
	import org.hamcrest.object.isFalse;
	import org.hamcrest.number.greaterThan;

	public class DebounceTest
	{
		[Test(async)]
		public function invokes_the_original_function_after_debounced_function_stops_being_called_for_wait_milliseconds():void
		{
			var dispatcher:EventDispatcher = new EventDispatcher();
			Async.proceedOnEvent(this, dispatcher, Event.COMPLETE, 1000);

			var start:int = getTimer();
			var complete:Function = function():void {
				assertThat(getTimer() - start, greaterThan(700));
				dispatcher.dispatchEvent(new Event(Event.COMPLETE));
			};

			var timer:Timer = new Timer(50, 8);
			timer.addEventListener(TimerEvent.TIMER, debounce(complete, 300));
			timer.start();
		}
	}
}

package asx.fn
{
	import flash.events.EventDispatcher;
	import flash.events.EventDispatcher;
	import flash.events.Event;
	import flash.events.TimerEvent;
	import flash.utils.getTimer;
	import flash.utils.Timer;

	import org.flexunit.async.Async;
	import org.flexunit.assertThat;
	import org.hamcrest.object.equalTo;
	import org.hamcrest.object.isFalse;
	import org.hamcrest.number.greaterThan;

	public class DebounceTest
	{
		[Test(async)]
		public function invokes_the_original_function_after_debounced_function_stops_being_called_for_wait_milliseconds():void
		{
			var dispatcher:EventDispatcher = new EventDispatcher();
			Async.proceedOnEvent(this, dispatcher, Event.COMPLETE, 1500);

			var start:int = getTimer();
			var complete:Function = function():void {
				assertThat(getTimer() - start, greaterThan(700));
				dispatcher.dispatchEvent(new Event(Event.COMPLETE));
			};

			var timer:Timer = new Timer(50, 8);
			timer.addEventListener(TimerEvent.TIMER, debounce(complete, 300));
			timer.start();
		}
	}
}

increase DebounceTest timeout due to imprecision of flash.utils.Timer


package com.threerings.io.streamers {

import com.threerings.util.ClassUtil;

import com.threerings.io.ArrayMask;
import com.threerings.io.ObjectInputStream;
import com.threerings.io.ObjectOutputStream;
import com.threerings.io.Streamer;
import com.threerings.io.Translations;
import com.threerings.io.TypedArray;

/**
 * A Streamer for Array objects.
 */
public class ArrayStreamer extends Streamer
{
    public function ArrayStreamer (jname :String = "[Ljava.lang.Object;")
    {
        super(TypedArray, jname);

        var secondChar :String = jname.charAt(1);
        if (secondChar === "[") {
            // if we're a multi-dimensional array then we need a delegate
            _delegate = Streamer.getStreamerByJavaName(jname.substring(1));
            _isFinal = true; // it just is

        } else if (secondChar === "L") {
            // form is "[L<class>;"
            var baseClass :String = jname.substring(2, jname.length - 1);
            baseClass = Translations.getFromServer(baseClass);
            _elementType = ClassUtil.getClassByName(baseClass);
            _isFinal = ClassUtil.isFinal(_elementType);

        } else if (secondChar === "I") {
            _elementType = int;

        } else {
            Log.getLog(this).warning("Other array types are " +
                "currently not handled yet [jname=" + jname + "].");
            throw new Error("Unimplemented bit");
        }
    }

    override public function isStreamerFor (obj :Object) :Boolean
    {
        if (obj is TypedArray) {
            // TypedArrays need the same element type
            return ((obj as TypedArray).getJavaType() === _jname);

        } else {
            // any other array is streamed as Object[]
            return (obj is Array) && (_jname === "[Ljava.lang.Object;");
        }
    }

    override public function isStreamerForClass (clazz :Class) :Boolean
    {
        if (clazz == TypedArray) {
            return false; // TODO: we're kinda fucked for finding a streamer
            // by class for TypedArrays here. The caller should be passing
            // the java name.

        } else {
            return ClassUtil.isAssignableAs(Array, clazz) &&
                (_jname === "[Ljava.lang.Object;");
        }
    }

    override public function createObject (ins :ObjectInputStream) :Object
    {
        var ta :TypedArray = new TypedArray(_jname);
        ta.length = ins.readInt();
        return ta;
    }

    override public function writeObject (obj :Object, out :ObjectOutputStream)
            :void
    {
        var arr :Array = (obj as Array);
        var ii :int;
        out.writeInt(arr.length);
        if (_elementType == int) {
            for (ii = 0; ii < arr.length; ii++) {
                out.writeInt(arr[ii] as int);
            }

        } else if (_isFinal) {
            var mask :ArrayMask = new ArrayMask(arr.length);
            for (ii = 0; ii < arr.length; ii++) {
                if (arr[ii] != null) {
                    mask.setBit(ii);
                }
            }
            mask.writeTo(out);
            // now write the populated elements
            for (ii = 0; ii < arr.length; ii++) {
                var element :Object = arr[ii];
                if (element != null) {
                    out.writeBareObjectImpl(element, _delegate);
                }
            }

        } else {
            for (ii = 0; ii < arr.length; ii++) {
                out.writeObject(arr[ii]);
            }
        }
    }

    override public function readObject (obj :Object, ins :ObjectInputStream)
            :void
    {
        var arr :Array = (obj as Array);
        var ii :int;
        if (_elementType == int) {
            for (ii = 0; ii < arr.length; ii++) {
                arr[ii] = ins.readInt();
            }

        } else if (_isFinal) {
            var mask :ArrayMask = new ArrayMask();
            mask.readFrom(ins);
            for (ii = 0; ii < length; ii++) {
                if (mask.isSet(ii)) {
                    var target :Object;
                    if (_delegate == null) {
                        target = new _elementType();
                    } else {
                        target = _delegate.createObject(ins);
                    }
                    ins.readBareObjectImpl(target, _delegate);
                    this[ii] = target;
                }
            }

        } else {
            for (ii = 0; ii < arr.length; ii++) {
                arr[ii] = ins.readObject();
            }
        }
    }

    /** A streamer for our elements. */
    protected var _delegate :Streamer;

    /** If this is the final dimension of the array, the element type. */
    protected var _elementType :Class;

    /** Whether we're final or not: true if we're not the final dimension
     * or if the element type is final. */
    protected var _isFinal :Boolean;
}
}

package com.threerings.io.streamers {

import com.threerings.util.ClassUtil;

import com.threerings.io.ArrayMask;
import com.threerings.io.ObjectInputStream;
import com.threerings.io.ObjectOutputStream;
import com.threerings.io.Streamer;
import com.threerings.io.Translations;
import com.threerings.io.TypedArray;

/**
 * A Streamer for Array objects.
 */
public class ArrayStreamer extends Streamer
{
    public function ArrayStreamer (jname :String = "[Ljava.lang.Object;")
    {
        super(TypedArray, jname);

        var secondChar :String = jname.charAt(1);
        if (secondChar === "[") {
            // if we're a multi-dimensional array then we need a delegate
            _delegate = Streamer.getStreamerByJavaName(jname.substring(1));
            _isFinal = true; // it just is

        } else if (secondChar === "L") {
            // form is "[L<class>;"
            var baseClass :String = jname.substring(2, jname.length - 1);
            baseClass = Translations.getFromServer(baseClass);
            _elementType = ClassUtil.getClassByName(baseClass);
            _isFinal = ClassUtil.isFinal(_elementType);

        } else if (secondChar === "I") {
            _elementType = int;

        } else if (secondChar === "B") {
            _elementType = Boolean;

        } else {
            Log.getLog(this).warning("Other array types are " +
                "currently not handled yet [jname=" + jname + "].");
            throw new Error("Unimplemented bit");
        }
    }

    override public function isStreamerFor (obj :Object) :Boolean
    {
        if (obj is TypedArray) {
            // TypedArrays need the same element type
            return ((obj as TypedArray).getJavaType() === _jname);

        } else {
            // any other array is streamed as Object[]
            return (obj is Array) && (_jname === "[Ljava.lang.Object;");
        }
    }

    override public function isStreamerForClass (clazz :Class) :Boolean
    {
        if (clazz == TypedArray) {
            return false; // TODO: we're kinda fucked for finding a streamer
            // by class for TypedArrays here. The caller should be passing
            // the java name.

        } else {
            return ClassUtil.isAssignableAs(Array, clazz) &&
                (_jname === "[Ljava.lang.Object;");
        }
    }

    override public function createObject (ins :ObjectInputStream) :Object
    {
        var ta :TypedArray = new TypedArray(_jname);
        ta.length = ins.readInt();
        return ta;
    }

    override public function writeObject (obj :Object, out :ObjectOutputStream)
            :void
    {
        var arr :Array = (obj as Array);
        var ii :int;
        out.writeInt(arr.length);
        if (_elementType == int) {
            for (ii = 0; ii < arr.length; ii++) {
                out.writeInt(arr[ii] as int);
            }

        } else if (_isFinal) {
            var mask :ArrayMask = new ArrayMask(arr.length);
            for (ii = 0; ii < arr.length; ii++) {
                if (arr[ii] != null) {
                    mask.setBit(ii);
                }
            }
            mask.writeTo(out);
            // now write the populated elements
            for (ii = 0; ii < arr.length; ii++) {
                var element :Object = arr[ii];
                if (element != null) {
                    out.writeBareObjectImpl(element, _delegate);
                }
            }

        } else {
            for (ii = 0; ii < arr.length; ii++) {
                out.writeObject(arr[ii]);
            }
        }
    }

    override public function readObject (obj :Object, ins :ObjectInputStream)
            :void
    {
        var arr :Array = (obj as Array);
        var ii :int;
        if (_elementType == int) {
            for (ii = 0; ii < arr.length; ii++) {
                arr[ii] = ins.readInt();
            }

        } else if (_isFinal) {
            var mask :ArrayMask = new ArrayMask();
            mask.readFrom(ins);
            for (ii = 0; ii < length; ii++) {
                if (mask.isSet(ii)) {
                    var target :Object;
                    if (_delegate == null) {
                        target = new _elementType();
                    } else {
                        target = _delegate.createObject(ins);
                    }
                    ins.readBareObjectImpl(target, _delegate);
                    this[ii] = target;
                }
            }

        } else {
            for (ii = 0; ii < arr.length; ii++) {
                arr[ii] = ins.readObject();
            }
        }
    }

    /** A streamer for our elements. */
    protected var _delegate :Streamer;

    /** If this is the final dimension of the array, the element type. */
    protected var _elementType :Class;

    /** Whether we're final or not: true if we're not the final dimension
     * or if the element type is final. */
    protected var _isFinal :Boolean;
}
}

Handle boolean arrays from the server.

Handle boolean arrays from the server.

git-svn-id: a1a4b28b82a3276cc491891159dd9963a0a72fae@4310 542714f4-19e9-0310-aa3c-eee0fc999fb1


/**
 * User: MerlinDS
 * Date: 18.07.2014
 * Time: 15:14
 */
package com.merlinds.miracle_tool.controllers {
	import com.merlinds.debug.log;
	import com.merlinds.miracle.meshes.MeshMatrix;
	import com.merlinds.miracle_tool.events.ActionEvent;
	import com.merlinds.miracle_tool.events.DialogEvent;
	import com.merlinds.miracle_tool.models.ProjectModel;
	import com.merlinds.miracle_tool.models.vo.AnimationVO;
	import com.merlinds.miracle_tool.models.vo.ElementVO;
	import com.merlinds.miracle_tool.models.vo.FrameVO;
	import com.merlinds.miracle_tool.models.vo.SourceVO;
	import com.merlinds.miracle_tool.models.vo.TimelineVO;
	import com.merlinds.miracle_tool.services.ActionService;
	import com.merlinds.miracle_tool.services.FileSystemService;
	import com.merlinds.miracle_tool.utils.MeshUtils;

	import flash.display.BitmapData;
	import flash.geom.Point;

	import flash.utils.ByteArray;

	import org.robotlegs.mvcs.Command;

	public class PublishCommand extends Command {

		[Inject]
		public var projectModel:ProjectModel;
		[Inject]
		public var fileSystemService:FileSystemService;
		[Inject]
		public var actionService:ActionService;
		[Inject]
		public var event:ActionEvent;

		private var _mesh:ByteArray;
		private var _png:ByteArray;
		private var _animations:ByteArray;
		//==============================================================================
		//{region							PUBLIC METHODS
		public function PublishCommand() {
			super();
		}

		override public function execute():void {
			log(this, "execute");
			if(event.body == null){
				var data:Object = {
					projectName:this.projectModel.name
				};
				this.actionService.addAcceptActions(new <String>[ActionEvent.PUBLISHING]);
				this.dispatch(new DialogEvent(DialogEvent.PUBLISH_SETTINGS, data));
			}else{
				var  projectName:String = event.body.projectName;
				this.createOutput();
				this.fileSystemService.writeTexture(projectName, _png, _mesh);
				this.fileSystemService.writeAnimation(_animations);
			}
		}

		//} endregion PUBLIC METHODS ===================================================

		//==============================================================================
		//{region						PRIVATE\PROTECTED METHODS
		private function createOutput():void{
			var buffer:BitmapData = new BitmapData(this.projectModel.outputSize, this.projectModel.outputSize, true, 0x0);
			var mesh:Array = [];
			var n:int = this.projectModel.sources.length;
			for(var i:int = 0; i < n; i++){
				var source:SourceVO = this.projectModel.sources[i];
				var m:int = source.elements.length;
				for(var j:int = 0; j < m; j++){
					//push element view to buffer
					var element:ElementVO = source.elements[j];
					var depth:Point = new Point(element.x + this.projectModel.boundsOffset,
									element.y + this.projectModel.boundsOffset);
					buffer.copyPixels(element.bitmapData, element.bitmapData.rect, depth);
					//get mesh
					mesh.push({
						name:element.name,
						vertexes:MeshUtils.flipToY(element.vertexes),
						uv:MeshUtils.covertToRelative(element.uv, this.projectModel.outputSize),
						indexes:element.indexes
					});
				}
				//get animation
				m = source.animations.length;
				var animations:Array = [];
				for(j = 0; j < m; j++){
					var animation:Object = this.createAnimationOutput(source.animations[j]);
					animations.push(animation);
				}
			}
			_mesh = new ByteArray();
			_mesh.writeObject(mesh);
			_png = PNGEncoder.encode(buffer);
			_animations = new ByteArray();
			_animations.writeObject(animations);
		}

		private function createAnimationOutput(animationVO:AnimationVO):Object {
			var data:Object = { name:animationVO.name.substr(0, -4),// name of the mesh
				totalFrames:animationVO.totalFrames,// Total animation frames
				layers:[]};
			var n:int = animationVO.timelines.length;
			//find matrix sequence for current animation
			for(var i:int = 0; i < n; i++){
				var timelineVO:TimelineVO = animationVO.timelines[i];
				var m:int = timelineVO.frames.length;
				var layer:Layer = new Layer();
				for(var j:int = 0; j < m; j++){
					var frameVO:FrameVO = timelineVO.frames[j];
					//create mesh
					var mesh:MeshMatrix = frameVO.matrix != null ? new MeshMatrix( 0, 0,
							frameVO.matrix.tx,
							frameVO.matrix.ty
							//TODO calculate scale and skew
						) : null;
					var index:int = mesh == null ? -1 : layer.meshes.push(mesh);
					var framesArray:Array = this.createFramesInfo(index, frameVO.name,
							frameVO.duration, frameVO.type == "motion");
					layer.frames = layer.frames.concat(framesArray);
				}
				data.layers.push(layer);
			}
			trace(JSON.stringify(data));
			return data;
		}

		[Inline]
		private function createFramesInfo(meshIndex:int, polygonName:String,
		                                  duration:int, motion:Boolean):Array {
			var result:Array = new Array(duration);
			if(duration < 2){
				motion = false;
			}
			if(meshIndex > -1){
				var t:Number = 1 / (duration - 1);
				for(var i:int = 0; i < duration; i++){
					var frameInfoData:FrameInfoData = new FrameInfoData();
					frameInfoData.meshIndex = meshIndex;
					frameInfoData.polygonName = polygonName;
					frameInfoData.motion = motion;
					frameInfoData.t = motion ? t * i : 0;
					result[i] = frameInfoData;
				}
			}
			return result;
		}
		//} endregion PRIVATE\PROTECTED METHODS ========================================

		//==============================================================================
		//{region							EVENTS HANDLERS
		//} endregion EVENTS HANDLERS ==================================================

		//==============================================================================
		//{region							GETTERS/SETTERS
		//} endregion GETTERS/SETTERS ==================================================
	}
}
class Layer{
	public var meshes:Array;
	public var frames:Array;

	public function Layer() {
		this.frames = [];
		this.meshes = [];
	}
}

class FrameInfoData{
	public var polygonName:String;
	public var motion:Boolean;
	public var meshIndex:int;
	public var t:Number;
}

/**
 * User: MerlinDS
 * Date: 18.07.2014
 * Time: 15:14
 */
package com.merlinds.miracle_tool.controllers {
	import com.merlinds.debug.log;
	import com.merlinds.miracle.meshes.MeshMatrix;
	import com.merlinds.miracle_tool.events.ActionEvent;
	import com.merlinds.miracle_tool.events.DialogEvent;
	import com.merlinds.miracle_tool.models.ProjectModel;
	import com.merlinds.miracle_tool.models.vo.AnimationVO;
	import com.merlinds.miracle_tool.models.vo.ElementVO;
	import com.merlinds.miracle_tool.models.vo.FrameVO;
	import com.merlinds.miracle_tool.models.vo.SourceVO;
	import com.merlinds.miracle_tool.models.vo.TimelineVO;
	import com.merlinds.miracle_tool.services.ActionService;
	import com.merlinds.miracle_tool.services.FileSystemService;
	import com.merlinds.miracle_tool.utils.MeshUtils;

	import flash.display.BitmapData;
	import flash.geom.Point;

	import flash.utils.ByteArray;

	import org.robotlegs.mvcs.Command;

	public class PublishCommand extends Command {

		[Inject]
		public var projectModel:ProjectModel;
		[Inject]
		public var fileSystemService:FileSystemService;
		[Inject]
		public var actionService:ActionService;
		[Inject]
		public var event:ActionEvent;

		private var _mesh:ByteArray;
		private var _png:ByteArray;
		private var _animations:ByteArray;
		//==============================================================================
		//{region							PUBLIC METHODS
		public function PublishCommand() {
			super();
		}

		override public function execute():void {
			log(this, "execute");
			if(event.body == null){
				var data:Object = {
					projectName:this.projectModel.name
				};
				this.actionService.addAcceptActions(new <String>[ActionEvent.PUBLISHING]);
				this.dispatch(new DialogEvent(DialogEvent.PUBLISH_SETTINGS, data));
			}else{
				var  projectName:String = event.body.projectName;
				this.createOutput();
				this.fileSystemService.writeTexture(projectName, _png, _mesh);
				this.fileSystemService.writeAnimation(_animations);
			}
		}

		//} endregion PUBLIC METHODS ===================================================

		//==============================================================================
		//{region						PRIVATE\PROTECTED METHODS
		private function createOutput():void{
			var buffer:BitmapData = new BitmapData(this.projectModel.outputSize, this.projectModel.outputSize, true, 0x0);
			var mesh:Array = [];
			var n:int = this.projectModel.sources.length;
			for(var i:int = 0; i < n; i++){
				var source:SourceVO = this.projectModel.sources[i];
				var m:int = source.elements.length;
				for(var j:int = 0; j < m; j++){
					//push element view to buffer
					var element:ElementVO = source.elements[j];
					var depth:Point = new Point(element.x + this.projectModel.boundsOffset,
									element.y + this.projectModel.boundsOffset);
					buffer.copyPixels(element.bitmapData, element.bitmapData.rect, depth);
					//get mesh
					mesh.push({
						name:element.name,
						vertexes:MeshUtils.flipToY(element.vertexes),
						uv:MeshUtils.covertToRelative(element.uv, this.projectModel.outputSize),
						indexes:element.indexes
					});
				}
				//get animation
				m = source.animations.length;
				var animations:Array = [];
				for(j = 0; j < m; j++){
					var animation:Object = this.createAnimationOutput(source.animations[j]);
					animations.push(animation);
				}
			}
			_mesh = new ByteArray();
			_mesh.writeObject(mesh);
			_png = PNGEncoder.encode(buffer);
			_animations = new ByteArray();
			_animations.writeObject(animations);
		}

		private function createAnimationOutput(animationVO:AnimationVO):Object {
			var data:Object = { name:animationVO.name.substr(0, -4),// name of the matrix
				totalFrames:animationVO.totalFrames,// Total animation frames
				layers:[]};
			var n:int = animationVO.timelines.length;
			//find matrix sequence for current animation
			for(var i:int = 0; i < n; i++){
				var timelineVO:TimelineVO = animationVO.timelines[i];
				var m:int = timelineVO.frames.length;
				var layer:Layer = new Layer();
				for(var j:int = 0; j < m; j++){
					var frameVO:FrameVO = timelineVO.frames[j];
					//create matrix
					var matrix:MeshMatrix = frameVO.matrix != null ? new MeshMatrix( 0, 0,
							frameVO.matrix.tx,
							frameVO.matrix.ty
							//TODO calculate scale and skew
						) : null;
					var index:int = matrix == null ? -1 : layer.matrixList.push(matrix) - 1;
					var framesArray:Array = this.createFramesInfo(index, frameVO.name,
							frameVO.duration, frameVO.type == "motion");
					layer.framesList = layer.framesList.concat(framesArray);
				}
				data.layers.push(layer);
			}
			trace(JSON.stringify(data));
			return data;
		}

		[Inline]
		private function createFramesInfo(index:int, polygonName:String,
		                                  duration:int, motion:Boolean):Array {
			var result:Array = new Array(duration);
			if(duration < 2){
				motion = false;
			}
			if(index > -1){
				var t:Number = 1 / (duration - 1);
				for(var i:int = 0; i < duration; i++){
					var frameInfoData:FrameInfoData = new FrameInfoData();
					frameInfoData.index = index;
					frameInfoData.polygonName = polygonName;
					frameInfoData.motion = motion;
					frameInfoData.t = motion ? t * i : 0;
					result[i] = frameInfoData;
				}
			}
			return result;
		}
		//} endregion PRIVATE\PROTECTED METHODS ========================================

		//==============================================================================
		//{region							EVENTS HANDLERS
		//} endregion EVENTS HANDLERS ==================================================

		//==============================================================================
		//{region							GETTERS/SETTERS
		//} endregion GETTERS/SETTERS ==================================================
	}
}
class Layer{
	public var matrixList:Array;
	public var framesList:Array;

	public function Layer() {
		this.framesList = [];
		this.matrixList = [];
	}
}

class FrameInfoData{
	public var polygonName:String;
	public var motion:Boolean;
	public var index:int;
	public var t:Number;
}

change format

change format


//
// $Id$

package com.threerings.flex {

import flash.display.DisplayObjectContainer;

import flash.events.Event;
import flash.events.FocusEvent;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.events.TextEvent;

import flash.ui.Keyboard;

import mx.containers.HBox;

import mx.core.Application;

import mx.controls.TextInput;

import mx.events.FlexEvent;

import com.threerings.util.ArrayUtil;
import com.threerings.util.StringUtil;

import com.threerings.flex.CommandButton;
import com.threerings.flex.CommandMenu;

import com.threerings.crowd.chat.client.ChatDirector;
import com.threerings.crowd.chat.data.ChatCodes;

import com.threerings.crowd.util.CrowdContext;

/**
 * The chat control widget.
 */
public class ChatControl extends HBox
{
    /**
     * Request focus for the oldest ChatControl.
     */
    public static function grabFocus () :void
    {
        if (_controls.length > 0) {
            (_controls[0] as ChatControl).setFocus();
        }
    }

    public function ChatControl (
        ctx :CrowdContext, sendButtonLabel :String = "send",
        height :Number = NaN, controlHeight :Number = NaN)
    {
        _ctx = ctx;
        _chatDtr = _ctx.getChatDirector();

        this.height = height;
        styleName = "chatControl";

        addChild(_txt = new ChatInput());
        _txt.addEventListener(FlexEvent.ENTER, sendChat, false, 0, true);
        _txt.addEventListener(KeyboardEvent.KEY_UP, handleKeyUp);

        _but = new CommandButton(sendButtonLabel, sendChat);
        addChild(_but);

        if (!isNaN(controlHeight)) {
            _txt.height = controlHeight;
            _but.height = controlHeight;
        }

        addEventListener(Event.ADDED_TO_STAGE, handleAddRemove);
        addEventListener(Event.REMOVED_FROM_STAGE, handleAddRemove);
    }

    /**
     * Provides access to the text field we use to accept chat.
     */
    public function get chatInput () :ChatInput
    {
        return _txt;
    }

    /**
     * Provides access to the send button.
     */
    public function get sendButton () :CommandButton
    {
        return _but;
    }

    override public function set enabled (en :Boolean) :void
    {
        super.enabled = en;
        if (_txt != null) {
            _txt.enabled = en;
            _but.enabled = en;
        }
    }

    /**
     * Request focus to this chat control.
     */
    override public function setFocus () :void
    {
        _txt.setFocus();
    }

    /**
     * Configures the chat director to which we should send our chat. Pass null to restore our
     * default chat director.
     */
    public function setChatDirector (chatDtr :ChatDirector) :void
    {
        _chatDtr = (chatDtr == null) ? _ctx.getChatDirector() : chatDtr;
    }

    /**
     * Configures the background color of the text entry area.
     */
    public function setChatColor (color :int) :void
    {
        _txt.setStyle("backgroundColor", color);
    }

    /**
     * Handles FlexEvent.ENTER and the action from the send button.
     */
    protected function sendChat (... ignored) :void
    {
        var message :String = StringUtil.trim(_txt.text);
        if ("" == message) {
            return;
        }

        var result :String = _chatDtr.requestChat(null, message, true);
        if (result != ChatCodes.SUCCESS) {
            _chatDtr.displayFeedback(null, result);
            return;
        }

        // if there was no error, clear the entry area in prep for the next entry event
        _txt.text = "";
        _histidx = -1;
    }

    protected function scrollHistory (next :Boolean) :void
    {
        var size :int = _chatDtr.getCommandHistorySize();
        if ((_histidx == -1) || (_histidx == size)) {
            _curLine = _txt.text;
            _histidx = size;
        }

        _histidx = (next) ? Math.min(_histidx + 1, size)
                          : Math.max(_histidx - 1, 0);
        var text :String = (_histidx == size) ? _curLine : _chatDtr.getCommandHistory(_histidx);
        _txt.text = text;
        _txt.setSelection(text.length, text.length);
    }

    /**
     * Handles Event.ADDED_TO_STAGE and Event.REMOVED_FROM_STAGE.
     */
    protected function handleAddRemove (event :Event) :void
    {
        if (event.type == Event.ADDED_TO_STAGE) {
            // set up any already-configured text
            _txt.text = _curLine;
            _histidx = -1;

            // request focus
            callLater(_txt.setFocus);
            _controls.push(this);

        } else {
            _curLine = _txt.text;
            ArrayUtil.removeAll(_controls, this);
        }
    }

    protected function handleKeyUp (event :KeyboardEvent) :void
    {
        switch (event.keyCode) {
        case Keyboard.UP: scrollHistory(false); break;
        case Keyboard.DOWN: scrollHistory(true); break;
        }
    }

    /** Our client-side context. */
    protected var _ctx :CrowdContext;

    /** The director to which we are sending chat requests. */
    protected var _chatDtr :ChatDirector;

    /** The place where the user may enter chat. */
    protected var _txt :ChatInput;

    /** The current index in the chat command history. */
    protected var _histidx :int = -1;

    /** The button for sending chat. */
    protected var _but :CommandButton;

    /** An array of the currently shown-controls. */
    protected static var _controls :Array = [];

    /** The preserved current line of text when traversing history or carried between instances of
     * ChatControl. */
    protected static var _curLine :String;
}
}

//
// $Id$

package com.threerings.flex {

import flash.display.DisplayObjectContainer;

import flash.events.Event;
import flash.events.FocusEvent;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;

import flash.ui.Keyboard;

import mx.containers.HBox;

import mx.core.Application;

import mx.controls.TextInput;

import mx.events.FlexEvent;

import com.threerings.util.ArrayUtil;
import com.threerings.util.MethodQueue;
import com.threerings.util.StringUtil;

import com.threerings.flex.CommandButton;
import com.threerings.flex.CommandMenu;

import com.threerings.crowd.chat.client.ChatDirector;
import com.threerings.crowd.chat.data.ChatCodes;

import com.threerings.crowd.util.CrowdContext;

/**
 * The chat control widget.
 */
public class ChatControl extends HBox
{
    /**
     * Request focus for the oldest ChatControl.
     */
    public static function grabFocus () :void
    {
        if (_controls.length > 0) {
            (_controls[0] as ChatControl).setFocus();
        }
    }

    public function ChatControl (
        ctx :CrowdContext, sendButtonLabel :String = "send",
        height :Number = NaN, controlHeight :Number = NaN)
    {
        _ctx = ctx;
        _chatDtr = _ctx.getChatDirector();

        this.height = height;
        styleName = "chatControl";

        addChild(_txt = new ChatInput());
        _txt.addEventListener(FlexEvent.ENTER, sendChat, false, 0, true);
        _txt.addEventListener(KeyboardEvent.KEY_UP, handleKeyUp);

        _but = new CommandButton(sendButtonLabel, sendChat);
        addChild(_but);

        if (!isNaN(controlHeight)) {
            _txt.height = controlHeight;
            _but.height = controlHeight;
        }

        addEventListener(Event.ADDED_TO_STAGE, handleAddRemove);
        addEventListener(Event.REMOVED_FROM_STAGE, handleAddRemove);
    }

    /**
     * Provides access to the text field we use to accept chat.
     */
    public function get chatInput () :ChatInput
    {
        return _txt;
    }

    /**
     * Provides access to the send button.
     */
    public function get sendButton () :CommandButton
    {
        return _but;
    }

    override public function set enabled (en :Boolean) :void
    {
        super.enabled = en;
        if (_txt != null) {
            _txt.enabled = en;
            _but.enabled = en;
        }
    }

    /**
     * Request focus to this chat control.
     */
    override public function setFocus () :void
    {
        _txt.setFocus();
    }

    /**
     * Configures the chat director to which we should send our chat. Pass null to restore our
     * default chat director.
     */
    public function setChatDirector (chatDtr :ChatDirector) :void
    {
        _chatDtr = (chatDtr == null) ? _ctx.getChatDirector() : chatDtr;
    }

    /**
     * Configures the background color of the text entry area.
     */
    public function setChatColor (color :int) :void
    {
        _txt.setStyle("backgroundColor", color);
    }

    /**
     * Handles FlexEvent.ENTER and the action from the send button.
     */
    protected function sendChat (... ignored) :void
    {
        var message :String = StringUtil.trim(_txt.text);
        if ("" == message) {
            return;
        }

        var result :String = _chatDtr.requestChat(null, message, true);
        if (result != ChatCodes.SUCCESS) {
            _chatDtr.displayFeedback(null, result);
            return;
        }

        // If there was no error, clear the entry area in prep for the next entry event
        MethodQueue.callLater(function () : void {
            // WORKAROUND
            // Originally we cleared the text immediately, but with flex 3.2 this broke
            // for *some* people. Weird! We're called from the event dispatcher for the
            // enter key, so it's possible that the default action is booching it?
            // In any case, this could possibly be removed in the future by the ambitious.
            // Note also: Flex's built-in callLater() doesn't work, but MethodQueue does. WTF?!?
            _txt.text = "";
        });
        _histidx = -1;
    }

    protected function scrollHistory (next :Boolean) :void
    {
        var size :int = _chatDtr.getCommandHistorySize();
        if ((_histidx == -1) || (_histidx == size)) {
            _curLine = _txt.text;
            _histidx = size;
        }

        _histidx = (next) ? Math.min(_histidx + 1, size)
                          : Math.max(_histidx - 1, 0);
        var text :String = (_histidx == size) ? _curLine : _chatDtr.getCommandHistory(_histidx);
        _txt.text = text;
        _txt.setSelection(text.length, text.length);
    }

    /**
     * Handles Event.ADDED_TO_STAGE and Event.REMOVED_FROM_STAGE.
     */
    protected function handleAddRemove (event :Event) :void
    {
        if (event.type == Event.ADDED_TO_STAGE) {
            // set up any already-configured text
            _txt.text = _curLine;
            _histidx = -1;

            // request focus
            callLater(_txt.setFocus);
            _controls.push(this);

        } else {
            _curLine = _txt.text;
            ArrayUtil.removeAll(_controls, this);
        }
    }

    protected function handleKeyUp (event :KeyboardEvent) :void
    {
        switch (event.keyCode) {
        case Keyboard.UP: scrollHistory(false); break;
        case Keyboard.DOWN: scrollHistory(true); break;
        }
    }

    /** Our client-side context. */
    protected var _ctx :CrowdContext;

    /** The director to which we are sending chat requests. */
    protected var _chatDtr :ChatDirector;

    /** The place where the user may enter chat. */
    protected var _txt :ChatInput;

    /** The current index in the chat command history. */
    protected var _histidx :int = -1;

    /** The button for sending chat. */
    protected var _but :CommandButton;

    /** An array of the currently shown-controls. */
    protected static var _controls :Array = [];

    /** The preserved current line of text when traversing history or carried between instances of
     * ChatControl. */
    protected static var _curLine :String;
}
}

Fix the chat input bug.
It's a mystery wrapped in a heisenbug inside sunspots.

git-svn-id: b675b909355d5cf946977f44a8ec5a6ceb3782e4@774 ed5b42cb-e716-0410-a449-f6a68f950b19


/*
 * Copyright 2014 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package flash.events {
[native(cls='EventClass')]
public class Event {
  public static const ACTIVATE:String = "activate";
  public static const ADDED:String = "added";
  public static const ADDED_TO_STAGE:String = "addedToStage";
  public static const CANCEL:String = "cancel";
  public static const CHANGE:String = "change";
  public static const CLEAR:String = "clear";
  public static const CLOSE:String = "close";
  public static const COMPLETE:String = "complete";
  public static const CONNECT:String = "connect";
  public static const COPY:String = "copy";
  public static const CUT:String = "cut";
  public static const DEACTIVATE:String = "deactivate";
  public static const ENTER_FRAME:String = "enterFrame";
  public static const FRAME_CONSTRUCTED:String = "frameConstructed";
  public static const EXIT_FRAME:String = "exitFrame";
  public static const FRAME_LABEL:String = "frameLabel";
  public static const ID3:String = "id3";
  public static const INIT:String = "init";
  public static const MOUSE_LEAVE:String = "mouseLeave";
  public static const OPEN:String = "open";
  public static const PASTE:String = "paste";
  public static const REMOVED:String = "removed";
  public static const REMOVED_FROM_STAGE:String = "removedFromStage";
  public static const RENDER:String = "render";
  public static const RESIZE:String = "resize";
  public static const SCROLL:String = "scroll";
  public static const TEXT_INTERACTION_MODE_CHANGE:String = "textInteractionModeChange";
  public static const SELECT:String = "select";
  public static const SELECT_ALL:String = "selectAll";
  public static const SOUND_COMPLETE:String = "soundComplete";
  public static const TAB_CHILDREN_CHANGE:String = "tabChildrenChange";
  public static const TAB_ENABLED_CHANGE:String = "tabEnabledChange";
  public static const TAB_INDEX_CHANGE:String = "tabIndexChange";
  public static const UNLOAD:String = "unload";
  public static const FULLSCREEN:String = "fullScreen";
  public static const CONTEXT3D_CREATE:String = "context3DCreate";
  public static const TEXTURE_READY:String = "textureReady";
  public static const VIDEO_FRAME:String = "videoFrame";
  public static const SUSPEND:String = "suspend";
  public static const CHANNEL_MESSAGE:String = "channelMessage";
  public static const CHANNEL_STATE:String = "channelState";
  public static const WORKER_STATE:String = "workerState";

  public function Event(type:String, bubbles:Boolean = false, cancelable:Boolean = false) {
    ctor(type, bubbles, cancelable);
  }

  public native function get type():String;

  public native function get bubbles():Boolean;

  public native function get cancelable():Boolean;

  public native function get target():Object;

  public native function get currentTarget():Object;

  public native function get eventPhase():uint;

  public function formatToString(className:String, ...args):String {
    var str = '[' + className;
    for (var i:uint = 0; i < args.length; i++) {
      var field:String = args[i];
      var value:Object = this[field];
      str += ' ' + field + '=' + value is String ? '"' + value + '"' : value;
    }
    return str + ']';
  }

  public function clone():Event {
    return new Event(type, bubbles, cancelable);
  }

  public function toString():String {
    return formatToString('Event', 'bubbles', 'cancelable', 'eventPhase');
  }

  public native function stopPropagation():void;

  public native function stopImmediatePropagation():void;

  public native function preventDefault():void;

  public native function isDefaultPrevented():Boolean;

  private native function ctor(type:String, bubbles:Boolean, cancelable:Boolean):void;
}
}

/*
 * Copyright 2014 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package flash.events {
[native(cls='EventClass')]
public class Event {
  public static const ACTIVATE:String = "activate";
  public static const ADDED:String = "added";
  public static const ADDED_TO_STAGE:String = "addedToStage";
  public static const CANCEL:String = "cancel";
  public static const CHANGE:String = "change";
  public static const CLEAR:String = "clear";
  public static const CLOSE:String = "close";
  public static const COMPLETE:String = "complete";
  public static const CONNECT:String = "connect";
  public static const COPY:String = "copy";
  public static const CUT:String = "cut";
  public static const DEACTIVATE:String = "deactivate";
  public static const ENTER_FRAME:String = "enterFrame";
  public static const FRAME_CONSTRUCTED:String = "frameConstructed";
  public static const EXIT_FRAME:String = "exitFrame";
  public static const FRAME_LABEL:String = "frameLabel";
  public static const ID3:String = "id3";
  public static const INIT:String = "init";
  public static const MOUSE_LEAVE:String = "mouseLeave";
  public static const OPEN:String = "open";
  public static const PASTE:String = "paste";
  public static const REMOVED:String = "removed";
  public static const REMOVED_FROM_STAGE:String = "removedFromStage";
  public static const RENDER:String = "render";
  public static const RESIZE:String = "resize";
  public static const SCROLL:String = "scroll";
  public static const TEXT_INTERACTION_MODE_CHANGE:String = "textInteractionModeChange";
  public static const SELECT:String = "select";
  public static const SELECT_ALL:String = "selectAll";
  public static const SOUND_COMPLETE:String = "soundComplete";
  public static const TAB_CHILDREN_CHANGE:String = "tabChildrenChange";
  public static const TAB_ENABLED_CHANGE:String = "tabEnabledChange";
  public static const TAB_INDEX_CHANGE:String = "tabIndexChange";
  public static const UNLOAD:String = "unload";
  public static const FULLSCREEN:String = "fullScreen";
  public static const CONTEXT3D_CREATE:String = "context3DCreate";
  public static const TEXTURE_READY:String = "textureReady";
  public static const VIDEO_FRAME:String = "videoFrame";
  public static const SUSPEND:String = "suspend";
  public static const CHANNEL_MESSAGE:String = "channelMessage";
  public static const CHANNEL_STATE:String = "channelState";
  public static const WORKER_STATE:String = "workerState";

  public function Event(type:String, bubbles:Boolean = false, cancelable:Boolean = false) {
    ctor(type, bubbles, cancelable);
  }

  public native function get type():String;

  public native function get bubbles():Boolean;

  public native function get cancelable():Boolean;

  public native function get target():Object;

  public native function get currentTarget():Object;

  public native function get eventPhase():uint;

  public function formatToString(className:String, ...args):String {
    var str = '[' + className;
    for (var i:uint = 0; i < args.length; i++) {
      var field:String = args[i];
      var value:Object = this[field];
      if (value is String) {
        value = '"' + value + '"';
      }
      str += ' ' + field + '=' + value;
    }
    return str + ']';
  }

  public function clone():Event {
    return new Event(type, bubbles, cancelable);
  }

  public function toString():String {
    return formatToString('Event', 'bubbles', 'cancelable', 'eventPhase');
  }

  public native function stopPropagation():void;

  public native function stopImmediatePropagation():void;

  public native function preventDefault():void;

  public native function isDefaultPrevented():Boolean;

  private native function ctor(type:String, bubbles:Boolean, cancelable:Boolean):void;
}
}

Fix Event#formatToString's output

Fix Event#formatToString's output


package dolly {

import dolly.core.dolly_internal;
import dolly.core.errors.CloningError;
import dolly.core.metadata.MetadataName;
import dolly.utils.PropertyUtil;

import flash.utils.Dictionary;

import org.as3commons.reflect.Accessor;
import org.as3commons.reflect.AccessorAccess;
import org.as3commons.reflect.Field;
import org.as3commons.reflect.Type;

use namespace dolly_internal;

public class Cloner {

	private static function isTypeCloneable(type:Type):Boolean {
		return type.hasMetadata(MetadataName.CLONEABLE);
	}

	private static function failIfTypeIsNotCloneable(type:Type):void {
		if (!isTypeCloneable(type)) {
			throw new CloningError(
					CloningError.CLASS_IS_NOT_CLONEABLE_MESSAGE,
					CloningError.CLASS_IS_NOT_CLONEABLE_CODE
			);
		}
	}

	dolly_internal static function findAllWritableFieldsForType(type:Type):Vector.<Field> {
		failIfTypeIsNotCloneable(type);

		const result:Vector.<Field> = new Vector.<Field>();

		for each(var field:Field in type.properties) {
			if (!(field is Accessor) || (field as Accessor).access == AccessorAccess.READ_WRITE) {
				result.push(field);
			}
		}

		return result;
	}

	dolly_internal static function doClone(source:*, deep:int, clonedObjectsMap:Dictionary = null, type:Type = null):* {
		if (!clonedObjectsMap) {
			clonedObjectsMap = new Dictionary();
		}

		if (!type) {
			type = Type.forInstance(source);
		}

		const fieldsToClone:Vector.<Field> = findAllWritableFieldsForType(type);
		const clonedInstance:* = new (type.clazz)();

		for each(var field:Field in fieldsToClone) {
			PropertyUtil.cloneProperty(source, clonedInstance, field.name);
		}

		return clonedInstance;
	}

	public static function clone(source:*):* {
		const type:Type = Type.forInstance(source);

		failIfTypeIsNotCloneable(type);

		const deep:int = 2;

		return doClone(source, deep, null, type);
	}
}
}

package dolly {

import dolly.core.dolly_internal;
import dolly.core.errors.CloningError;
import dolly.core.metadata.MetadataName;
import dolly.utils.ClassUtil;
import dolly.utils.PropertyUtil;

import flash.utils.ByteArray;

import flash.utils.Dictionary;

import org.as3commons.reflect.Accessor;
import org.as3commons.reflect.AccessorAccess;
import org.as3commons.reflect.Field;
import org.as3commons.reflect.Type;

use namespace dolly_internal;

public class Cloner {

	private static var _isClassPreparedForCloningMap:Dictionary = new Dictionary();

	private static function isTypeCloneable(type:Type):Boolean {
		return type.hasMetadata(MetadataName.CLONEABLE);
	}

	private static function isTypePreparedForCloning(type:Type):Boolean {
		return _isClassPreparedForCloningMap[type.clazz];
	}

	private static function failIfTypeIsNotCloneable(type:Type):void {
		if (!isTypeCloneable(type)) {
			throw new CloningError(
					CloningError.CLASS_IS_NOT_CLONEABLE_MESSAGE,
					CloningError.CLASS_IS_NOT_CLONEABLE_CODE
			);
		}
	}

	private static function prepareTypeForCloning(type:Type):void {
		ClassUtil.registerAliasFor(type.clazz);

		_isClassPreparedForCloningMap[type.clazz] = true;
	}

	dolly_internal static function findAllWritableFieldsForType(type:Type):Vector.<Field> {
		failIfTypeIsNotCloneable(type);

		if (!isTypePreparedForCloning(type)) {
			prepareTypeForCloning(type);
		}

		const result:Vector.<Field> = new Vector.<Field>();

		for each(var field:Field in type.properties) {
			if (!(field is Accessor) || (field as Accessor).access == AccessorAccess.READ_WRITE) {
				result.push(field);
			}
		}

		return result;
	}

	dolly_internal static function doClone(source:*, deep:int, clonedObjectsMap:Dictionary = null, type:Type = null):* {
		if (!clonedObjectsMap) {
			clonedObjectsMap = new Dictionary();
		}

		if (!type) {
			type = Type.forInstance(source);
		}

		if(!isTypePreparedForCloning(type)) {
			prepareTypeForCloning(type);
		}

		const fieldsToClone:Vector.<Field> = findAllWritableFieldsForType(type);

		for each(var field:Field in fieldsToClone) {
//			PropertyUtil.cloneProperty(source, clonedInstance, field.name);
			var fieldType:Type = field.type;
			if(isTypeCloneable(fieldType) && !isTypePreparedForCloning(fieldType)) {
				prepareTypeForCloning(fieldType);
			}
		}

		const byteArray:ByteArray = new ByteArray();
		byteArray.writeObject(source);
		byteArray.position = 0;

		const clonedInstance:* = byteArray.readObject();

		return clonedInstance;
	}

	public static function clone(source:*):* {
		const type:Type = Type.forInstance(source);

		failIfTypeIsNotCloneable(type);

		const deep:int = 2;

		return doClone(source, deep, null, type);
	}
}
}

Switch Cloner class to cloning of objects by using ByteArray.


//
// $Id$
//
// Vilya library - tools for developing networked games
// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/vilya/
//
// 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.parlor.client {

import com.threerings.util.ArrayUtil;
import com.threerings.util.ObserverList;
import com.threerings.util.Util;

import com.threerings.presents.client.BasicDirector;
import com.threerings.presents.client.Client;
import com.threerings.presents.client.ClientEvent;
import com.threerings.presents.client.InvocationService_ResultListener;

import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.EntryAddedEvent;
import com.threerings.presents.dobj.EntryRemovedEvent;
import com.threerings.presents.dobj.EntryUpdatedEvent;
import com.threerings.presents.dobj.SetListener;

import com.threerings.crowd.data.BodyObject;
import com.threerings.crowd.data.PlaceObject;

import com.threerings.parlor.data.Table;
import com.threerings.parlor.data.TableConfig;
import com.threerings.parlor.data.TableLobbyObject;
import com.threerings.parlor.game.data.GameConfig;
import com.threerings.parlor.util.ParlorContext;

/**
 * As tables are created and managed within the scope of a place (a lobby), we want to fold the
 * table management functionality into the standard hierarchy of place controllers that deal with
 * place-related functionality on the client. Thus, instead of forcing places that expect to have
 * tables to extend a TableLobbyController or something similar, we instead provide
 * the table director which can be instantiated by the place controller (or specific table related
 * views) to handle the table matchmaking services.
 *
 *
 Entites that do so, will need to implement the {@link TableObserver} interface so that the
 * table director can notify them when table related things happen.
 *
 *
 The table services expect that the place object being used as a lobby in which the table
 * matchmaking takes place implements the {@link TableLobbyObject} interface.
 */
public class TableDirector extends BasicDirector
    implements SetListener, InvocationService_ResultListener
{
    protected static const log :Log = Log.getLog(TableDirector);

    /**
     * Creates a new table director to manage tables with the specified observer which will receive
     * callbacks when interesting table related things happen.
     *
     * @param ctx the parlor context in use by the client.
     * @param tableField the field name of the distributed set that contains the tables we will be
     * managing.
     * @param observer the entity that will receive callbacks when things happen to the tables.
     */
    public function TableDirector (ctx :ParlorContext, tableField :String)
    {
        super(ctx);

        // keep track of this stuff
        _pctx = ctx;
        _tableField = tableField;
    }

    /**
     * This must be called by the entity that uses the table director when the using entity
     * prepares to enter and display a "place".
     */
    public function setTableObject (tlobj :DObject) :void
    {
        // the distributed object should be a TableLobbyObject
        _tlobj = TableLobbyObject(tlobj);
        // listen for table set updates
        tlobj.addListener(this);
    }

    /**
     * This must be called by the entity that uses the table director when the using entity has
     * left and is done displaying the "place".
     */
    public function clearTableObject () :void
    {
        // remove our listenership and clear out
        if (_tlobj != null) {
            (_tlobj as DObject).removeListener(this);
            _tlobj = null;
        }
    }

    /**
     * Requests that the specified observer be added to the list of observers that are notified
     * of table updates
     */
    public function addTableObserver (observer :TableObserver) :void
    {
        _tableObservers.add(observer);
    }

    /**
     * Requests that the specified observer be removed from to the list of observers that are
     * notified of table updates
     */
    public function removeTableObserver (observer :TableObserver) :void
    {
        _tableObservers.remove(observer);
    }

    /**
     * Requests that the specified observer be added to the list of observers that are notified
     * when this client sits down at or stands up from a table.
     */
    public function addSeatednessObserver (observer :SeatednessObserver) :void
    {
        _seatedObservers.add(observer);
    }

    /**
     * Requests that the specified observer be removed from to the list of observers that are
     * notified when this client sits down at or stands up from a table.
     */
    public function removeSeatednessObserver (observer :SeatednessObserver) :void
    {
        _seatedObservers.remove(observer);
    }

    /**
     * Returns true if this client is currently seated at a table, false if they are not.
     */
    public function isSeated () :Boolean
    {
        return (_ourTable != null);
    }

    /**
     * Returns the table this client is currently seated at, or null.
     */
     public function getSeatedTable () :Table
     {
         return _ourTable;
     }

    /**
     * Sends a request to create a table with the specified game configuration. This user will
     * become the owner of this table and will be added to the first position in the table. The
     * response will be communicated via the {@link TableObserver} interface.
     */
    public function createTable (tableConfig :TableConfig, config :GameConfig) :void
    {
        // if we're already in a table, refuse the request
        if (_ourTable != null) {
            log.warning("Ignoring request to create table as we're " +
                        "already in a table [table=" + _ourTable + "].");
            return;
        }

        // make sure we're currently in a place
        if (_tlobj == null) {
            log.warning("Requested to create a table but we're not " +
                        "currently in a place [config=" + config + "].");
            return;
        }

        // go ahead and issue the create request
        _tlobj.getTableService().createTable(_pctx.getClient(), tableConfig, config, this);
    }

    /**
     * Sends a request to join the specified table at the specified position. The response will be
     * communicated via the {@link TableObserver} interface.
     */
    public function joinTable (tableId :int, position :int) :void
    {
        // if we're already in a table, refuse the request
        if (_ourTable != null) {
            log.warning("Ignoring request to join table as we're " +
                        "already in a table [table=" + _ourTable + "].");
            return;
        }

        // make sure we're currently in a place
        if (_tlobj == null) {
            log.warning("Requested to join a table but we're not " +
                        "currently in a place [tableId=" + tableId + "].");
            return;
        }

        // issue the join request
        log.info("Joining table [tid=" + tableId + ", pos=" + position + "].");
        _tlobj.getTableService().joinTable(_pctx.getClient(), tableId, position, this);
    }

    /**
     * Sends a request to leave the specified table at which we are presumably seated. The response
     * will be communicated via the {@link TableObserver} interface.
     */
    public function leaveTable (tableId :int) :void
    {
        // make sure we're currently in a place
        if (_tlobj == null) {
            log.warning("Requested to leave a table but we're not " +
                        "currently in a place [tableId=" + tableId + "].");
            return;
        }

        // issue the leave request
        _tlobj.getTableService().leaveTable(_pctx.getClient(), tableId, this);
    }

    /**
     * Sends a request to have the specified table start now, even if all the seats have not yet
     * been filled.
     */
    public function startTableNow (tableId :int) :void
    {
        if (_tlobj == null) {
            log.warning("Requested to start a table but we're not " +
                        "currently in a place [tableId=" + tableId + "].");
            return;
        }

        _tlobj.getTableService().startTableNow(_ctx.getClient(), tableId, this);
    }

    // documentation inherited
    override public function clientDidLogoff (event :ClientEvent) :void
    {
        super.clientDidLogoff(event);
        _tlobj = null;
        _ourTable = null;
    }

    // documentation inherited
    override protected function fetchServices (client :Client) :void
    {
    }

    // documentation inherited
    public function entryAdded (event :EntryAddedEvent) :void
    {
        if (event.getName() == _tableField) {
            var table :Table = (event.getEntry() as Table);
            // check to see if we just joined a table
            checkSeatedness(table);
            // now let the observers know what's up
            _tableObservers.apply(function (to :TableObserver) :void {
                to.tableAdded(table);
            });
        }
    }

    // documentation inherited
    public function entryUpdated (event :EntryUpdatedEvent) :void
    {
        if (event.getName() == _tableField) {
            var table :Table = (event.getEntry() as Table);
            // check to see if we just joined or left a table
            checkSeatedness(table);
            // now let the observers know what's up
            _tableObservers.apply(function (to :TableObserver) :void {
                to.tableUpdated(table);
            });
        }
    }

    // documentation inherited
    public function entryRemoved (event :EntryRemovedEvent) :void
    {
        if (event.getName() == _tableField) {
            var tableId :int = (event.getKey() as int);
            // check to see if our table just disappeared
            if (_ourTable != null && tableId == _ourTable.tableId) {
                _ourTable = null;
                notifySeatedness(false);
            }
            // now let the observer know what's up
            _tableObservers.apply(function (to :TableObserver) :void {
                to.tableRemoved(tableId);
            });
        }
    }

    // documentation inherited from interface
    public function requestProcessed (result :Object) :void
    {
        var tableId :int = int(result);
        if (_tlobj == null) {
            // we've left, it's none of our concern anymore
            log.info("Table created, but no lobby. [tableId=" + tableId + "].");
            return;
        }

        var table :Table = (_tlobj.getTables().get(tableId) as Table);
        if (table == null) {
            log.warning("Table created, but where is it? [tableId=" + tableId + "]");
            return;
        }

        // All this to check to see if we created a party game (and should now enter).
        if (table.gameOid != -1 && (table.occupants == null)) {
            // let's boogie!
            _pctx.getParlorDirector().gameIsReady(table.gameOid);
        }
    }

    // documentation inherited from interface
    public function requestFailed (reason :String) :void
    {
        log.warning("Table action failed [reason=" + reason + "].");
    }

    /**
     * Checks to see if we're a member of this table and notes it as our table, if so.
     */
    protected function checkSeatedness (table :Table) :void
    {
        var oldTable :Table = _ourTable;

        // if this is the same table as our table, clear out our table reference and allow it to be
        // added back if we are still in the table
        if (table.equals(_ourTable)) {
            _ourTable = null;
        }

        // look for our username in the occupants array
        var self :BodyObject = (_pctx.getClient().getClientObject() as BodyObject);
        if (ArrayUtil.contains(table.occupants, self.getVisibleName())) {
            _ourTable = table;
        }

        // if nothing changed, bail now
        if (Util.equals(oldTable, _ourTable)) {
            return;
        }

        // otherwise notify the observers
        notifySeatedness(_ourTable != null);
    }

    /**
     * Notifies the seatedness observers of a seatedness change.
     */
    protected function notifySeatedness (isSeated :Boolean) :void
    {
        _seatedObservers.apply(function (so :SeatednessObserver) :void {
            so.seatednessDidChange(isSeated);
        });
    }

    /** A context by which we can access necessary client services. */
    protected var _pctx :ParlorContext;

    /** The place object, cast as a TableLobbyObject. */
    protected var _tlobj :TableLobbyObject;

    /** The field name of the distributed set that contains our tables. */
    protected var _tableField :String;

    /** The table of which we are a member if any. */
    protected var _ourTable :Table;

    /** An array of entities that want to hear about table updates. */
    protected var _tableObservers :ObserverList = new ObserverList();

    /** An array of entities that want to hear about when we stand up or sit down. */
    protected var _seatedObservers :ObserverList = new ObserverList();
}
}

//
// $Id$
//
// Vilya library - tools for developing networked games
// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/vilya/
//
// 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.parlor.client {

import com.threerings.util.ArrayUtil;
import com.threerings.util.ObserverList;
import com.threerings.util.Util;

import com.threerings.presents.client.BasicDirector;
import com.threerings.presents.client.Client;
import com.threerings.presents.client.ClientEvent;
import com.threerings.presents.client.InvocationService_ResultListener;

import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.EntryAddedEvent;
import com.threerings.presents.dobj.EntryRemovedEvent;
import com.threerings.presents.dobj.EntryUpdatedEvent;
import com.threerings.presents.dobj.SetListener;

import com.threerings.crowd.data.BodyObject;
import com.threerings.crowd.data.PlaceObject;

import com.threerings.parlor.data.Table;
import com.threerings.parlor.data.TableConfig;
import com.threerings.parlor.data.TableLobbyObject;
import com.threerings.parlor.game.data.GameConfig;
import com.threerings.parlor.util.ParlorContext;

/**
 * As tables are created and managed within the scope of a place (a lobby), we want to fold the
 * table management functionality into the standard hierarchy of place controllers that deal with
 * place-related functionality on the client. Thus, instead of forcing places that expect to have
 * tables to extend a TableLobbyController or something similar, we instead provide
 * the table director which can be instantiated by the place controller (or specific table related
 * views) to handle the table matchmaking services.
 *
 *
 Entites that do so, will need to implement the {@link TableObserver} interface so that the
 * table director can notify them when table related things happen.
 *
 *
 The table services expect that the place object being used as a lobby in which the table
 * matchmaking takes place implements the {@link TableLobbyObject} interface.
 */
public class TableDirector extends BasicDirector
    implements SetListener, InvocationService_ResultListener
{
    protected static const log :Log = Log.getLog(TableDirector);

    /**
     * Creates a new table director to manage tables with the specified observer which will receive
     * callbacks when interesting table related things happen.
     *
     * @param ctx the parlor context in use by the client.
     * @param tableField the field name of the distributed set that contains the tables we will be
     * managing.
     * @param observer the entity that will receive callbacks when things happen to the tables.
     */
    public function TableDirector (ctx :ParlorContext, tableField :String)
    {
        super(ctx);

        // keep track of this stuff
        _pctx = ctx;
        _tableField = tableField;
    }

    /**
     * This must be called by the entity that uses the table director when the using entity
     * prepares to enter and display a "place".
     */
    public function setTableObject (tlobj :DObject) :void
    {
        // the distributed object should be a TableLobbyObject
        _tlobj = TableLobbyObject(tlobj);
        // listen for table set updates
        tlobj.addListener(this);
    }

    /**
     * This must be called by the entity that uses the table director when the using entity has
     * left and is done displaying the "place".
     */
    public function clearTableObject () :void
    {
        // remove our listenership and clear out
        if (_tlobj != null) {
            (_tlobj as DObject).removeListener(this);
            _tlobj = null;
            _ourTable = null;
        }
    }

    /**
     * Requests that the specified observer be added to the list of observers that are notified
     * of table updates
     */
    public function addTableObserver (observer :TableObserver) :void
    {
        _tableObservers.add(observer);
    }

    /**
     * Requests that the specified observer be removed from to the list of observers that are
     * notified of table updates
     */
    public function removeTableObserver (observer :TableObserver) :void
    {
        _tableObservers.remove(observer);
    }

    /**
     * Requests that the specified observer be added to the list of observers that are notified
     * when this client sits down at or stands up from a table.
     */
    public function addSeatednessObserver (observer :SeatednessObserver) :void
    {
        _seatedObservers.add(observer);
    }

    /**
     * Requests that the specified observer be removed from to the list of observers that are
     * notified when this client sits down at or stands up from a table.
     */
    public function removeSeatednessObserver (observer :SeatednessObserver) :void
    {
        _seatedObservers.remove(observer);
    }

    /**
     * Returns true if this client is currently seated at a table, false if they are not.
     */
    public function isSeated () :Boolean
    {
        return (_ourTable != null);
    }

    /**
     * Returns the table this client is currently seated at, or null.
     */
     public function getSeatedTable () :Table
     {
         return _ourTable;
     }

    /**
     * Sends a request to create a table with the specified game configuration. This user will
     * become the owner of this table and will be added to the first position in the table. The
     * response will be communicated via the {@link TableObserver} interface.
     */
    public function createTable (tableConfig :TableConfig, config :GameConfig) :void
    {
        // if we're already in a table, refuse the request
        if (_ourTable != null) {
            log.warning("Ignoring request to create table as we're " +
                        "already in a table [table=" + _ourTable + "].");
            return;
        }

        // make sure we're currently in a place
        if (_tlobj == null) {
            log.warning("Requested to create a table but we're not " +
                        "currently in a place [config=" + config + "].");
            return;
        }

        // go ahead and issue the create request
        _tlobj.getTableService().createTable(_pctx.getClient(), tableConfig, config, this);
    }

    /**
     * Sends a request to join the specified table at the specified position. The response will be
     * communicated via the {@link TableObserver} interface.
     */
    public function joinTable (tableId :int, position :int) :void
    {
        // if we're already in a table, refuse the request
        if (_ourTable != null) {
            log.warning("Ignoring request to join table as we're " +
                        "already in a table [table=" + _ourTable + "].");
            return;
        }

        // make sure we're currently in a place
        if (_tlobj == null) {
            log.warning("Requested to join a table but we're not " +
                        "currently in a place [tableId=" + tableId + "].");
            return;
        }

        // issue the join request
        log.info("Joining table [tid=" + tableId + ", pos=" + position + "].");
        _tlobj.getTableService().joinTable(_pctx.getClient(), tableId, position, this);
    }

    /**
     * Sends a request to leave the specified table at which we are presumably seated. The response
     * will be communicated via the {@link TableObserver} interface.
     */
    public function leaveTable (tableId :int) :void
    {
        // make sure we're currently in a place
        if (_tlobj == null) {
            log.warning("Requested to leave a table but we're not " +
                        "currently in a place [tableId=" + tableId + "].");
            return;
        }

        // issue the leave request
        _tlobj.getTableService().leaveTable(_pctx.getClient(), tableId, this);
    }

    /**
     * Sends a request to have the specified table start now, even if all the seats have not yet
     * been filled.
     */
    public function startTableNow (tableId :int) :void
    {
        if (_tlobj == null) {
            log.warning("Requested to start a table but we're not " +
                        "currently in a place [tableId=" + tableId + "].");
            return;
        }

        _tlobj.getTableService().startTableNow(_ctx.getClient(), tableId, this);
    }

    // documentation inherited
    override public function clientDidLogoff (event :ClientEvent) :void
    {
        super.clientDidLogoff(event);
        _tlobj = null;
        _ourTable = null;
    }

    // documentation inherited
    override protected function fetchServices (client :Client) :void
    {
    }

    // documentation inherited
    public function entryAdded (event :EntryAddedEvent) :void
    {
        if (event.getName() == _tableField) {
            var table :Table = (event.getEntry() as Table);
            // check to see if we just joined a table
            checkSeatedness(table);
            // now let the observers know what's up
            _tableObservers.apply(function (to :TableObserver) :void {
                to.tableAdded(table);
            });
        }
    }

    // documentation inherited
    public function entryUpdated (event :EntryUpdatedEvent) :void
    {
        if (event.getName() == _tableField) {
            var table :Table = (event.getEntry() as Table);
            // check to see if we just joined or left a table
            checkSeatedness(table);
            // now let the observers know what's up
            _tableObservers.apply(function (to :TableObserver) :void {
                to.tableUpdated(table);
            });
        }
    }

    // documentation inherited
    public function entryRemoved (event :EntryRemovedEvent) :void
    {
        if (event.getName() == _tableField) {
            var tableId :int = (event.getKey() as int);
            // check to see if our table just disappeared
            if (_ourTable != null && tableId == _ourTable.tableId) {
                _ourTable = null;
                notifySeatedness(false);
            }
            // now let the observer know what's up
            _tableObservers.apply(function (to :TableObserver) :void {
                to.tableRemoved(tableId);
            });
        }
    }

    // documentation inherited from interface
    public function requestProcessed (result :Object) :void
    {
        var tableId :int = int(result);
        if (_tlobj == null) {
            // we've left, it's none of our concern anymore
            log.info("Table created, but no lobby. [tableId=" + tableId + "].");
            return;
        }

        var table :Table = (_tlobj.getTables().get(tableId) as Table);
        if (table == null) {
            log.warning("Table created, but where is it? [tableId=" + tableId + "]");
            return;
        }

        // All this to check to see if we created a party game (and should now enter).
        if (table.gameOid != -1 && (table.occupants == null)) {
            // let's boogie!
            _pctx.getParlorDirector().gameIsReady(table.gameOid);
        }
    }

    // documentation inherited from interface
    public function requestFailed (reason :String) :void
    {
        log.warning("Table action failed [reason=" + reason + "].");
    }

    /**
     * Checks to see if we're a member of this table and notes it as our table, if so.
     */
    protected function checkSeatedness (table :Table) :void
    {
        var oldTable :Table = _ourTable;

        // if this is the same table as our table, clear out our table reference and allow it to be
        // added back if we are still in the table
        if (table.equals(_ourTable)) {
            _ourTable = null;
        }

        // look for our username in the occupants array
        var self :BodyObject = (_pctx.getClient().getClientObject() as BodyObject);
        if (ArrayUtil.contains(table.occupants, self.getVisibleName())) {
            _ourTable = table;
        }

        // if nothing changed, bail now
        if (Util.equals(oldTable, _ourTable)) {
            return;
        }

        // otherwise notify the observers
        notifySeatedness(_ourTable != null);
    }

    /**
     * Notifies the seatedness observers of a seatedness change.
     */
    protected function notifySeatedness (isSeated :Boolean) :void
    {
        _seatedObservers.apply(function (so :SeatednessObserver) :void {
            so.seatednessDidChange(isSeated);
        });
    }

    /** A context by which we can access necessary client services. */
    protected var _pctx :ParlorContext;

    /** The place object, cast as a TableLobbyObject. */
    protected var _tlobj :TableLobbyObject;

    /** The field name of the distributed set that contains our tables. */
    protected var _tableField :String;

    /** The table of which we are a member if any. */
    protected var _ourTable :Table;

    /** An array of entities that want to hear about table updates. */
    protected var _tableObservers :ObserverList = new ObserverList();

    /** An array of entities that want to hear about when we stand up or sit down. */
    protected var _seatedObservers :ObserverList = new ObserverList();
}
}

Make sure our seatedness variable accurately reflects the departure of our table.

git-svn-id: a3e1eb16dde062992de22c830ed8045c8013209a@441 c613c5cb-e716-0410-b11b-feb51c14d237


//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/nenya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

package com.threerings.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(classXml.@classId, ClassRecord.fromXml(classXml));
        }

        return pos;
    }

    /** Our mapping from class names to class records. */
    protected var _classes :Map = Maps.newMapOf(int);
}
}

//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/nenya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

package com.threerings.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);
}
}

Fix bug in indexing the color classes map.

git-svn-id: b675b909355d5cf946977f44a8ec5a6ceb3782e4@993 ed5b42cb-e716-0410-a449-f6a68f950b19