Newer
Older
exporter / Utils.as
package 
{
	/* This class has functions similar to the Utils.as in the /flash/Dialogs folder. However, some of 
	*  those functions are different here than in /flash/Dialogs/Utils.as for... reasons? flash folder reasons!
	*/
	import flash.utils.*;
	import flash.net.*;
	import flash.display.*;
	import flash.text.TextField;
	import flash.text.TextFormat;
	import flash.text.TextFieldType;
	import flash.text.TextFieldAutoSize;
	import flash.text.TextFormatAlign;
	import flash.geom.Point;
	import flash.geom.Matrix;
	import flash.geom.Rectangle;
	import flash.geom.ColorTransform;
	import fl.motion.Color;
	import fl.transitions.Tween;
	import fl.transitions.easing.*;
	import fl.transitions.TweenEvent;
	import flash.net.navigateToURL;
	import flash.external.ExternalInterface;
	import flash.system.Capabilities;
	import flash.filters.*;
	import flash.filters.ColorMatrixFilter;
	import com.adobe.images.JPGEncoder;
	import flash.globalization.DateTimeFormatter;
	
	import djarts.utils.Base64;
	import djarts.utils.PNGEnc;
	import User.UserOptions;
	
	public class Utils 
	{
		//["K","M","B","T","q", "Q", "s", "S","O","N","d","U","D","!","@","#","$","%","^","&","*"]
		static var symbols = ["K","M","B","t","q", "Q", "s", "S","o","n","d","U","D","T","Qt","Qd","Sd","St","O","N","v","c"];
		static var divisors = [];
		
		public static function FormatBigNumber(number:*, round:Boolean=false):String
		{
			if (number < 0) return '-' + FormatBigNumber(Math.abs(number), round);
		
			var isWholeNumber = (Math.floor(number) == number);
			round = round || isWholeNumber;
		
			if (number < 1000)
			{
				if (round)
				{
					return Math.round(number).toString();
				}
				else
				{
					var decimalPlaces = (number >= 100) ? 1 : 2; // Below 100, show two decimal places. Above, only one.
					var roundMult = Math.pow(10, decimalPlaces);
					var rounded = Math.round(number);
					var bigRounded = Math.round(number * roundMult);
					var roundedDifferenceProportion = Math.abs(bigRounded / roundMult - rounded) / rounded;
					
					if (roundedDifferenceProportion < 0.001)
					{
						// Not a whole number, but the decimal places aren't worth showing
						// Below also handles this, but is slower
						return rounded.toString();
					}
					else
					{
						var str:String = bigRounded.toString();
						str = str.substr(0, str.length - decimalPlaces) + "." + str.substr(str.length - decimalPlaces);
						
						while (str.charAt(str.length - 1) == "0") str = str.substr(0, str.length - 1); //should only ever happen once
						if (str.charAt(str.length - 1) == ".") str = str.substr(0, str.length - 1); //probably won't happen at all
						
						return str;
					}
				}
			}
			
			// Determine which symbol to use
			var power = Math.floor(Math.log(number) / Math.log(1000));
			var index = Math.min(power-1, symbols.length-1);
			var amount = number / Math.pow(1000, index+1);
			var symbol = (index >= 0 && index < symbols.length) ? symbols[index] : "";
			
			// If it's too big for a symbol (or if we just WANT to)
			if (amount >= 1000 || UserOptions.ForceScientific)
			{
				// scientific notation
				var power = Math.floor(Math.log(number) / Math.log(10));
				var num = number / Math.pow(10, power);
				if (num == 10) {
					num = 1;
					power += 1;
				}
				var numString = "" + num;
				numString = numString.substr(0, Math.min(4, numString.length));
				return numString + "e" + power;
				
			} 
			else 
			{
				// Format with the symbol
				// determine number of whole digits (will dictate how many decimals we show)
				var whole = Math.floor(amount);
				var amount_str:String = String(whole);
				
				var decimalDivider = 100/Math.pow(10,amount_str.length-1);
				
				//round to nearest decimal place
				amount = int(amount*decimalDivider)/decimalDivider;
				
				var amountString:String = amount.toString();
				
				var noDecimal:String = amountString.replace(".","");
				if (noDecimal.length == 2) 
				{
					if (amountString.indexOf(".") != -1) amountString += "0";
					else amountString += ".0";
				}
				else if (noDecimal.length == 1) amountString += ".00";
				
				return amountString+symbol; // concat symbol
			}
		}
		
		private static var BigNumberRegex =  new RegExp(/(\d*),?(\d*)(.*)/);
		// Note-DG: This is mostly just used for definitions in the database, that don't require precision.
		//	This way we can define values in the database as 100B instead of 100000000000. 
		public static function ParseBigNumberString(number:String):Number
		{
			var regexArray:Array = BigNumberRegex.exec(number);
			var magnitude = symbols.indexOf(regexArray[3]);
			var thousands:Number; 
			if (regexArray[1]) {
				thousands = regexArray[1];
				if (regexArray[2]) { // if numbers are split by comma, multiply by 1000
					thousands *= 1000;
				}
			} else {
				thousands = 0;
			}
			var hundreds:Number =  regexArray[2] ? regexArray[2] : 0;
			
			return (thousands + hundreds) * Math.pow(1000, magnitude + 1);
		}
		
		public static function ParseNumberRange(range:String):Array
		{
			var split:Array = range.split(',');
			return [Number(split[0]), Number(split[1])];
		}
				
		public static function DesaturateFilter()
		{
			var desatMatrix:Array = [];
			var cmFilter;
				
			desatMatrix = desatMatrix.concat([0.309, 0.609, 0.082, 0, 0]); // red
			desatMatrix = desatMatrix.concat([0.309, 0.609, 0.082, 0, 0]); // green
			desatMatrix = desatMatrix.concat([0.309, 0.609, 0.082, 0, 0]); // blue
			desatMatrix = desatMatrix.concat([0, 0, 0, 1, 0]); // alpha
			cmFilter = new ColorMatrixFilter(desatMatrix);
			
			return cmFilter;
		}
		
		public static function GetUserAgent():String
        {
            try
            {
                var userAgent = ExternalInterface.call("window.navigator.userAgent.toString");
                var browser:String = "[Unknown Browser]";
     
                if (userAgent.indexOf("Safari") != -1)
                {
                    browser = "Safari";
                }
                if (userAgent.indexOf("Firefox") != -1)
                {
                    browser = "Firefox";
                }
                if (userAgent.indexOf("Chrome") != -1)
                {
                    browser = "Chrome";
                }
                if (userAgent.indexOf("MSIE") != -1)
                {
                    browser = "Internet Explorer";
                }
                if (userAgent.indexOf("Opera") != -1)
                {
                    browser = "Opera";
                }
            }
            catch (e:Error)
            {
                //could not access ExternalInterface in containing page
                return "[No ExternalInterface]";
            }
 
            return browser;
        }
		
		public static function GetScreenshot(stage:Stage):String {
			var scale:Number = 0.25;
			var result:String = null;
			var blurFilter:BlurFilter = new BlurFilter(3, 3, BitmapFilterQuality.HIGH);
			
			var bData:BitmapData = new BitmapData(stage.stageWidth * scale,
												  stage.stageHeight * scale, false, 0x348400);
			var matrix:Matrix = new Matrix();
			matrix.scale(scale, scale);
			
			bData.draw(stage, matrix);
			bData.applyFilter(bData, bData.rect, new Point(0, 0), blurFilter);
			
			
			var imgBytes:ByteArray = new JPGEncoder(80).encode(bData);
			//var imgBytes:ByteArray = PNGEnc.encode(bData);
			if (imgBytes) {
				var screenshotBase64:String = Base64.encode(imgBytes);
				if (screenshotBase64) {
					result = screenshotBase64;
				}
			}
			
			return result;
		}
		
		public static function RecursivelyStop(clip, frame = null)
		{
			if (clip is MovieClip)
			{
				if (frame == null)
				{
					clip.stop();
				} else {
					clip.gotoAndStop(frame);
				}
			}
			if (clip is DisplayObjectContainer)
			{
				for (var i = 0; i < clip.numChildren; i++)
				{				
					RecursivelyStop(clip.getChildAt(i));
				}
			}
		}
		
		public static function PlayerVersion()
		{
			
			// Get the player's version by using the getVersion() global function.
			var versionNumber:String = Capabilities.version;
			
			// The version number is a list of items divided by ","
			var versionArray:Array = versionNumber.split(",");
			var length:Number = versionArray.length;
			//for(var i:Number = 0; i < length; i++) _strace("versionArray["+i+"]: "+versionArray[i]);
			
			// The main version contains the OS type too so we split it in two
			// and we'll have the OS type and the major version number separately.
			var platformAndVersion:Array = versionArray[0].split(" ");
			//for(var j:Number = 0; j < 2; j++) _strace("platformAndVersion["+j+"]: "+platformAndVersion[j]);
			//_strace("-----");
			
			var majorVersion:Number = parseInt(platformAndVersion[1]);
			var minorVersion:Number = parseInt(versionArray[1]);
			var buildNumber:Number = parseInt(versionArray[2]);
			
			return Number(majorVersion+"."+minorVersion);
		}
		
		
		public static function OpenLocalURLTop(url)
		{
			navigateToURL(new URLRequest(GameSettings.Approot+url), "_top" );
		}
		
		public static function OpenLocalURL(url)
		{
			navigateToURL(new URLRequest(GameSettings.Approot+url), "_blank" );
		}
		
		public static function OpenURL(url, window = "_blank")
		{
			navigateToURL(new URLRequest(url), window);
		}
		
		public static var ExitFullScreenCallback;
		
		public static function WallPublish(title:String, desc1:String, desc2:String, image:String, url = null, action = null)
		{
			if (Utils.ExitFullScreenCallback) Utils.ExitFullScreenCallback();
			//_strace(title + "\n" + desc1 + "\n" + desc2 + "\n" + image + "\n");
			ExternalInterface.call("askToPublish", title, url, desc1, desc2, image, action);
			
			//if (Settings.Platform == Settings.HI5) {
				//DialogManager.Instance().ShowMessageBox("The message was posted to your wall!", "", "OK", null);
			//}
		}
		
		public static function SendToRecipients(recipients:Array, description, data)
		{
			if (!GameSettings.IsFacebook) return;
			
			if (Utils.ExitFullScreenCallback) Utils.ExitFullScreenCallback();
			try {
				ExternalInterface.call("sendToRecipients", recipients, description ,data);
			} 
			catch (e)
			{
				_strace(e.message);
			}
		}
		

		public static function GetFriendData()
		{
			try {
				return ExternalInterface.call("pollInviteData");
			} 
			catch (e)
			{
				_strace(e.message);
			}
		}
		
		
		public static function singleBounceEase(t:Number, b:Number, c:Number, d:Number):Number
		{
			t = t / d;
			if (t < 0.7) {
				t *= 1.0 / 0.65;
				return c * (t * t) + b;
			}
			return c * (t * t) + b;
		}
		
		public static function Finish(e:TweenEvent)
		{
			//_strace("done  " + e.currentTarget.obj.x + " " + e.currentTarget.obj.y + " " + e.currentTarget.obj.scaleX + " " + e.currentTarget.obj.scaleY);
			e.currentTarget.removeEventListener(TweenEvent.MOTION_FINISH, Finish);
		}
		public static function Popup(mc, startScale = 0.8, finishCallback = null)
		{
			var rect:Rectangle = mc.getRect(mc);
			var targetX = mc.x;
			var targetY = mc.y;
			var centerX = mc.x + rect.left + mc.width / 2.0;
			var centerY = mc.y + rect.top + mc.height / 2.0;
			var startX = centerX - (rect.left + mc.width / 2.0) * startScale;
			var startY = centerY - (rect.top + mc.height / 2.0) * startScale;
			var tweenSX = new Tween(mc, "scaleX", singleBounceEase, startScale, 1.0, 4, false);
			if (finishCallback) tweenSX.addEventListener(TweenEvent.MOTION_FINISH, finishCallback);
			var tweenSY = new Tween(mc, "scaleY", singleBounceEase, startScale, 1.0, 4, false);
			if (finishCallback) tweenSY.addEventListener(TweenEvent.MOTION_FINISH, finishCallback);
			var tweenX = new Tween(mc, "x", singleBounceEase, startX, targetX, 4, false);
			if (finishCallback) tweenX.addEventListener(TweenEvent.MOTION_FINISH, finishCallback);
			var tweenY = new Tween(mc, "y", singleBounceEase, startY, targetY, 4, false);
			if (finishCallback) tweenY.addEventListener(TweenEvent.MOTION_FINISH, finishCallback);
			return [tweenSX, tweenSY, tweenX, tweenY];
		}
		public static function StringSplit(s:String, delim:String, max:int = -1):Array
		{
			var ret:Array = s.split(delim);
			if (max != -1) {
				var more:String = ret.slice(max - 1).join(delim);
				ret.splice(max - 1);
				ret[max - 1] = more;
			}
			return ret;
		}
		
		public static function StringStartsWith(str:String, substr:String):Boolean
		{
			if (substr.length > str.length) return false;
			return (str.substr(0, substr.length) == substr);
		}
		
		public static function StringEndsWith(str:String, substr:String):Boolean
		{
			if (substr.length > str.length) return false;
			return (str.substr(str.length - substr.length, substr.length) == substr);
		}
		
		public static function StringContains(str:String, substr:String):Boolean
		{
			return (str.indexOf(substr) !== -1);
		}
		
		private static var vowelsAndH = {
			"a":true, "A":true,
			"e":true, "E":true,
			"i":true, "I":true,
			"o":true, "O":true,
			"u":true, "U":true,
			"h":true, "H":true
		}
		public static function StringStartsWithVowelOrH(s:String):Boolean
		{
			if (vowelsAndH[s.charAt(0)]) return true;
			return false;
		}
		
		public static function CopyObject(obj, except = null)
		{
			var i;
			if (except == null) except = [];
			if (obj is Array) {
				var obj2 = [];
				for (i = 0; i < obj.length; i++) {
					obj2.push(Utils.CopyObject(obj[i]));
				}
				return obj2;
			}
			var ret:Object = {};
			for (i in obj)
			{
				if (except.indexOf(i) == -1) {
					ret[i] = obj[i];
				}
			}
			
			return ret;
		}
		public static function CopyToObject(objFrom, objTo, except = null)
		{
			if (except == null) except = [];
			for (var i in objFrom)
			{
				if (except.indexOf(i) == -1) {
					objTo[i] = objFrom[i];
				}
			}
		}
		public static function CopyToObjectOnly(objFrom, objTo, only)
		{
			if (only == null) return;
			for (var i in only)
			{
				if (objFrom.hasOwnProperty(only[i]))
				{
					objTo[only[i]] = objFrom[only[i]];
				}
			}
		}
		
		public static function ColorizeRGB(clip:MovieClip, r:int,g:int,b:int)
		{
			var cTransform = clip.transform.colorTransform; 
			cTransform.redOffset = r;
			cTransform.greenOffset = g;
			cTransform.blueOffset = b;
			clip.transform.colorTransform = cTransform;
		}
		
		public static function hex2rgb (hex):Object
		{
			var red = hex>>16;
			var greenBlue = hex-(red<<16)
			var green = greenBlue>>8;
			var blue = greenBlue - (green << 8);
			return({r:red, g:green, b:blue});
		}
		
		public static function RGBtoHEX(r, g, b) {
			return r << 16 | g << 8 | b;
		}
		
		/**
		 * NOTE: An alpha of 1 will make the object a solid color (good for silhouettes)
		 */
		public static function TintDisplayObject(obj:DisplayObject, color:uint = 0xffffff, alpha:Number = 1.0)
		{
			var cTint:Color = new Color();
			cTint.setTint(color, alpha);
			obj.transform.colorTransform = cTint;
		}
		
		// given an origin (ox, oy) and a look at point (tx, ty),
		// and a clip with the given number of rotation states, which
		// rotation should we pick?
		public static function Rotation(ox, oy, tx, ty, rotations)
		{
			var delta:Point = new Point(tx - ox, ty - oy);
			if (Point.distance(delta, new Point(0.0, 0.0)) < 0.00001) {
				delta.y = -1.0;
			}
			var d = (
					 (-Math.floor(
								  /* calculate the rotation and scale it */
								  Math.atan2(delta.y, delta.x) / (Math.PI * 2.0) * rotations
								  + 0.5	// add 0.5 and take the floor (round to closest int)
								  )
					  + rotations) // atan2 returns in the range -pi to pi, measured ccw from +x,
					 			   // we negate it and add rotations again to get it in the range
								   // [0, rotations]
					 % rotations) + 1;	// add 1 to reference frames
			return d;
		}
		/* performs multiple operations asynchronously, but wait for them all to finish:
		 * instead of calling a bunch of functions and passing callbacks, pass those callbacks to WaitForFunctions
		 * and use ret.callbacks[0], ret.callbacks[1], etc... as the callbacks
		 * this will wait until every command is complete and call all the callbacks at once.
		 * 
		 * For instance, to load multiple graphics packs asynchronously and wait for them all to finish,
		 * call var ret = WaitForFunctions([DoneLoading, null, null, null]); to generate
		 * ret.callbacks[0]..ret.callbacks[3], and pass these to 4 calls to LoadExternalGraphics.
		 * When these 4 calls complete, the provided 4 functions will be called with the arguments returned
		 * from the callbacks (in this case, only DoneLoading will be called since the
		 * other callbacks are null).
		 */
		public static function WaitForFunctions(...funcs):Object
		{
			var ret:Object = {};
			// has a callback been hit?
			ret.hit = new Array(funcs.length);
			// what were the original arguments the callback was called with
			ret.args = new Array(funcs.length);
			// a list of the artifical callbacks we are providing
			ret.callbacks = new Array(funcs.length);
			// the real callbacks
			ret.funcs = funcs;
			ret.done = false;
			
			ret.immediate = [];
			
			for (var i = 0; i < funcs.length; i++) {
				// we have to setup this temp callback in another function, because the anon function's
				// activation object will contain the current scope, meaning that the variable
				// i will be updated in each function; if we do it in a seperate function each
				// activation object will reference a different i
				ret.callbacks[i] = Utils.SetupFunction(ret, i);
			}
			return ret;
		}
		
		/**
		 * Returns the standard money format for an integer amount
		 */
		public static function FormatCurrency(amount:int, symbol:String = "$"):String
		{
			var afterDecimal:String = (amount % 100).toString();
			while (afterDecimal.length < 2) afterDecimal = "0" + afterDecimal; //mmmm string concatenation
			
			var beforeDecimal:String;
			if (amount > 100)
			{
				beforeDecimal = Math.floor(amount/100).toString();
			}
			else
			{
				beforeDecimal = "0";
			}
			
			return symbol + beforeDecimal + "." + afterDecimal;
		}
		
		public static function FormatNumber(amount){
			var whole = Math.floor(amount);
			// convert the number to a string
			var amount_str:String = String(whole);
			var total_str:String = String(amount);
			
			var number_array:Array = [];
			var start:Number;
			var end:Number = amount_str.length;
			while (end > 0) {
				start = Math.max(end - 3, 0);
				number_array.unshift(amount_str.slice(start, end));
				end = start;
			}
			
			var point = total_str.indexOf(".");

			var ret = number_array.join(",");
			if (point != -1 && point < total_str.length)
			{
				ret += total_str.substr(point);
			}

			return ret;
		}
		
		protected static function SetupFunction(ret, i):Function
		{
			var a:Function =
				function(...rest) {
					if (ret.immediate[i]) ret.immediate[i].apply(null, rest);
					ret.hit[i] = true;
					ret.args[i] = rest;
					var ok:Boolean = true;
					var j;
					for (j = 0; j < ret.hit.length; j++) {
						if (!ret.hit[j]) { ok = false; break; }
					}
					if (ok && !ret.done) {
						ret.done = true;
						for (j = 0; j < ret.funcs.length; j++) {
							if (ret.funcs[j] != null) ret.funcs[j].apply(null, ret.args[j]);
						}
					}
				};
			return a;
		}
		
		// useful for the paned dialogs to show a certain pane/button
		public static function OnlyVisible(obj, set:Array, index:int)
		{
			for (var i in set) {
				if (i != index) {
					obj[set[i]].visible = false;
				} else {
					obj[set[i]].visible = true;
				}
			}
		}
		public static function AllVisible(obj, set:Array)
		{
			for (var i in set) {
				obj[set[i]].visible = true;
			}
		}
		// set one of a list of tabs to be enabled
		public static function OnlyToggled(obj, set:Array, index:int)
		{
			for (var i in set) {
				if (i != index) {
					obj[set[i]].SetToggled(false);
				} else {
					obj[set[i]].SetToggled(true);
				}
			}
		}
		// get the frame number of a label in the timeline
		public static function GetLabelFrame(mc, name:String):int
		{
			for (var l in mc.currentLabels) {
				if (mc.currentLabels[l].name == name) return mc.currentLabels[l].frame;
			}
			return 1;
		}
		// generate a quick text field of a certain color/style
		public static function BasicTextField(color:uint,
											  size:int,
											  font:String,
											  text:String,
											  additional:Object = null,
											  align = "left"):TextField
		{
			var t:TextField = new TextField();
			var f:TextFormat = new TextFormat();
			f.align = align;
			if (additional != null) for (var a in additional) f[a] = additional[a];
			f.font = font;
			f.color = color;
			f.size = size;
			t.type = TextFieldType.DYNAMIC;
			t.defaultTextFormat = f;
			t.text = text;
			t.selectable = false;
			t.autoSize = TextFieldAutoSize.LEFT;
			return t;
		}
		public static function var_dump(_obj, name = "Dump")
		{	
			_strace(name + " " + (_obj) + " {");
			flash.utils.describeType(_obj);
			var varList:XMLList = flash.utils.describeType(_obj)..variable;
			
			for(var i:int; i < varList.length(); i++)
			{
				//Show the name and the value 
				_strace("  " + varList[i].@name+' = '+ _obj[varList[i].@name]);
			}
			_strace("}");
		}

		public static function SetChildFrames(parentClip, frame, exclude = null)
		{
			if (parentClip is MovieClip && parentClip.name != exclude)
			{
				parentClip.gotoAndStop(frame);
				for (var i = 0; i < parentClip.numChildren; i++)
				{
					Utils.SetChildFrames(parentClip.getChildAt(i), frame, exclude);
				}
			}
		}
		
		public static function SetChildFramesNoParent(parentClip, frame, exclude = null)
		{
			if (parentClip is MovieClip && parentClip.name != exclude)
			{
				for (var i = 0; i < parentClip.numChildren; i++)
				{
					Utils.SetChildFrames(parentClip.getChildAt(i), frame, exclude);
				}
			}
		}
		
		//modifies the array passed as parameter, following the MO of the built in Array.sort()
		public static function insertionSort(array:Array, compareFunction:Function):void
		{
			for(var i:int=1; i < array.length; i++)
			{
				var value:Object = array[i];
				var k:int=i-1;
				while( k >= 0 && compareFunction(array[k], value) == -1)
				{
					array[k+1]=array[k];
					k--;
				}
				array[k+1]=value;
			}
		}
		

		public static function Screenshot(sourceClip, receiverURL, filename)
		{
			
			var jpgSource:BitmapData; 
			
			if (sourceClip is Stage)
			{
				jpgSource = new BitmapData (sourceClip.stageWidth, sourceClip.stageHeight);
			} else {
				jpgSource = new BitmapData (sourceClip.width, sourceClip.height);
			}
			
			jpgSource.draw(sourceClip);
			
			var jpgEncoder:JPGEncoder = new JPGEncoder(85);
			var jpgStream:ByteArray = jpgEncoder.encode(jpgSource);
			
			var header:URLRequestHeader = new URLRequestHeader("Content-type", "application/octet-stream");
			var jpgURLRequest:URLRequest = new URLRequest(receiverURL+"?name="+filename);
			jpgURLRequest.requestHeaders.push(header);
			jpgURLRequest.method = URLRequestMethod.POST;
			jpgURLRequest.data = jpgStream;
			navigateToURL(jpgURLRequest, "_blank");

		}

		
		public static function SetColors(clip, color)
		{
			var colorTransform:ColorTransform;
			
			if (clip == null || color == null) return;
			
			if (clip.hasOwnProperty("outline") && clip.outline != null)
			{
				colorTransform = clip.outline.transform.colorTransform;
				colorTransform.color = color.Outline;
				clip.outline.transform.colorTransform = colorTransform;
			}
			
			if (clip.hasOwnProperty("secondaryOutline") && clip.secondaryOutline != null)
			{
				colorTransform = clip.secondaryOutline.transform.colorTransform;
				colorTransform.color = color.LightOutline;
				clip.secondaryOutline.transform.colorTransform = colorTransform;
			}
			
			if (clip.hasOwnProperty("color") && clip.color != null)
			{
				colorTransform = clip.color.transform.colorTransform;
				colorTransform.color = color.Primary;
				clip.color.transform.colorTransform = colorTransform;
			}
			
			if (clip.hasOwnProperty("secondaryColor") && clip.secondaryColor != null)
			{
				colorTransform = clip.secondaryColor.transform.colorTransform;
				colorTransform.color = color.Secondary;
				clip.secondaryColor.transform.colorTransform = colorTransform;
			}
			
			if (clip.hasOwnProperty("shadow") && clip.shadow != null)
			{
				colorTransform = clip.shadow.transform.colorTransform;
				colorTransform.color = color.Shadow;
				clip.shadow.transform.colorTransform = colorTransform;
			}
			
			if (clip.hasOwnProperty("secondaryShadow") && clip.secondaryShadow != null)
			{
				colorTransform = clip.secondaryShadow.transform.colorTransform;
				colorTransform.color = color.SecondaryShadow;
				clip.secondaryShadow.transform.colorTransform = colorTransform;
			}
		}
		/* returns a rect with the x, and y pos to center and the scaleX and scaleY as width and height */
		public static function GetUniformScaleRect(clip:MovieClip, maxWidth, maxHeight)
		{
			var rect:Rectangle = new Rectangle(0,0,0,0);
			
			var widthScale = Math.min(maxWidth / clip.width, 999);
			var heightScale = Math.min(maxHeight / clip.height, 999);
			
			var scale = 1;
			if (widthScale > heightScale)
			{
				scale = heightScale;
			} else {
				scale = widthScale;
			}
			
			rect.width = clip.width * scale;
			rect.height = clip.height * scale;
			
			var pb = clip.getBounds(clip);
			var xadjust = -pb.left * scale;
			var yadjust = -pb.top * scale;
			
			rect.x = xadjust + (maxWidth - rect.width) / 2;
			rect.y = yadjust + (maxHeight - rect.height) / 2;
			
			rect.width = scale;
			rect.height = scale;
			
			return rect;
		}
		
		public static function Dec2Hex(d)
		{
			var chars:String = "0123456789ABCDEF";
			var str:String = "";
			while (d > 0 || str.length < 2)
			{
				var digit = d % 16;
				d = (d - digit) / 16;
				str = chars.charAt(digit) + str;
			}
			return str;
		}
		
		public static function Hex2Dec(h:String)
		{
			var chars:String = "0123456789ABCDEF";
			var val = 1;
			var res = 0;
			for (var i = h.length - 1; i >= 0; i--)
			{
				res += chars.indexOf(h.substr(i, 1)) * val;
				val *= 16;
			}
			return res;
		}
		
		public static function ShallowCopyArray(array:Array)
		{
			if (!array) return null;
				
			var newArray:Array = [];
			for (var i:int = 0; i < array.length; i++)
			{
				newArray.push(array[i]);
			}
			return newArray;
		}
		
		/* Note: Will fail if any items in either array have reference type semantics (unless they point to the same object)
		*  (as opposed to value type semantics)
		*/
		public static function areEqual(a:Array,b:Array):Boolean {
			if(a.length != b.length) {
				return false;
			}
			var len:int = a.length;
			for(var i:int = 0; i < len; i++) {
				if(a[i] !== b[i]) {
					return false;
				}
			}
			return true;
		}
		
		/* Note: Will fail if any items in either array have reference type semantics 
		*  (as opposed to value type semantics)
		*/
		public static function objectsAreEqual(a:Object,b:Object):Boolean {
			
			for (var i in a)
			{
				if (!b.hasOwnProperty(i) || a[i] != b[i]) return false;
			}
			
			return true;
		}
				
		public static function DeepCopyClone (source : Object) : *
        {
            var array : ByteArray = new ByteArray ();

            array.writeObject (source);
            array.position = 0;

            return array.readObject ();
        }
		
		// {a:1, b:2} => [1,2]
		public static function ObjectValues (obj:Object):Array {
			var array = [];
			for (var key in obj) array.push(obj[key]);
			return array;
		}
		
		// {a:1, b:2} => ["a","b"]
		public static function ObjectKeys (obj:Object):Array {
			var array = [];
			for (var key in obj) array.push(key);
			return array;
		}
		
		// normal random variate generator; mean m, standard deviation s
		public static function BoxMullerRandomNormal(m:Number, s:Number) : Number 
		{				       
			var x1, x2, w, y1:Number;
			var y2:Number;
			var use_last:Boolean = false;

			if (use_last == true)		        /* use value from previous call */
			{
				y1 = y2;
				use_last = false;
			}
			else
			{
				do {
					x1 = 2.0 * Math.random() - 1.0;
					x2 = 2.0 * Math.random() - 1.0;
					w = x1 * x1 + x2 * x2;
				} while ( w >= 1.0 );

				w = Math.sqrt( (-2.0 * Math.log( w ) ) / w );
				y1 = x1 * w;
				y2 = x2 * w;
				use_last = true;
			}

			return( m + y1 * s );
		}
		
		public static function SanitizeNumber(n)
		{
			if (n is Number)
			{
				if (n == Number.NEGATIVE_INFINITY || n == Number.POSITIVE_INFINITY || n == Number.NaN) return 0;
			}
			return n;
		}
		
		public static function IsNumberInvalid(n)
		{
			if (n is Number)
			{
				if (n == Number.NEGATIVE_INFINITY || n == Number.POSITIVE_INFINITY || n == Number.NaN) return true;
			}
			return false;
		}
		
		public static function LimitNumberToMax(n)
		{
			if (n == Number.POSITIVE_INFINITY) return Number.MAX_VALUE;
			return n;
		}
		
		/*
			value= Math.round(20/7);	
			value= int((20/7)*10)/10;	
			value= int((20/7)*100)/100;	
			value= int((20/7)*1000)/1000;	
			value= int((20/7)*10000)/10000;
		*/
		public static function RoundToDecimalPlaces(number:Number, places:int)
		{
			return Math.round((number)*Math.pow(10,places))/Math.pow(10,places);
		}
		
		public static function GetShuffledArray(a:Array) : Array
		{
			var r:Array = a.concat();
			r.sort(randomSort);
			return r;
		}
		
		///Convenience function since I keep forgetting the name of randomSort (hint: it's randomSort)
		public static function ShuffleArray(a:Array)
		{
			a.sort(randomSort);
		}
		
		public static function PickRandom(a:Array)
		{
			return a[Math.floor(Math.random() * a.length)];
		}
		
		/**
		 * Returns n elements of array a, up to a maximum of a's length.
		 * If forceCopy is set, the returned array will always be a new instance.
		 */
		public static function PickRandomSet(a:Array, n:int, forceCopy:Boolean=false):Array
		{	
			if (a.length <= n || a.length == 0) 
			{
				if (forceCopy) return a.concat();
				else return a;
			}
			if (n == 1) return [PickRandom(a)];
			
			var shuffled:Array = GetShuffledArray(a);
			return shuffled.slice(0, n);
		}
		
		/**
		 * Does not randomly sort an array! To do that, Use the default array.sort with this as its argument.
		 * If you want a new, shuffled array, use GetShuffledArray
		 */
		public static function randomSort(a:*, b:*) : Number
		{
			if (Math.random() < 0.5) return -1;
			else return 1;
		}
		
		/**
		 * Returns the union of arr1 and arr2. If the elements are not primitive types, you'll need to provide a sorting function.
		 * If inPlace, a1 and a2 will be sorted.
		 * If they already contain duplicates, then all bets are off.
		 */
		public static function MergeArrays(arr1:Array, arr2:Array, sortFunc:Function = null, inPlace:Boolean = false) : Array
		{	
			var ret:Array = [];
			var a1:Array = (inPlace) ? arr1 : arr1.slice();
			var a2:Array = (inPlace) ? arr2 : arr2.slice();
			
			var len1:uint = a1.length;
			var len2:uint = a2.length;
			var i1:uint = 0;
			var i2:uint = 0;
			
			//Boring cases
			if (len1 == 0 && len2 == 0) return ret;
			if (len1 == 0) return (inPlace) ? a2.slice() : a2;
			if (len2 == 0) return (inPlace) ? a1.slice() : a1;
			
			if (sortFunc)
			{
				a1.sort(sortFunc);
				a2.sort(sortFunc);
			}
			else
			{
				a1.sort();
				a2.sort();
			}
			
			while (i1 < len1)
			{
				//add all from a2 up to a1[i1]
				while (i2 < len2 && a2[i2] <= a1[i1])
				{
					ret.push(a2[i2]);
					i2 += 1;
				}
				if (ret.length == 0 || a1[i1] != ret[ret.length-1]) ret.push(a1[i1]);
				i1 += 1;
			}
			
			while (i2 < len2) ret.push(a2[i2]);
			
			return ret;
		}
		
		public static function ConvertToHHMMSS(seconds:Number):String
		{
			var s:Number = seconds % 60;
			var m:Number = Math.floor((seconds % 3600 ) / 60);
			var h:Number = Math.floor(seconds / (60 * 60));
			 
			var hourStr:String = (h == 0) ? "" : DoubleDigitFormat(h) + ":";
			var minuteStr:String = DoubleDigitFormat(m) + ":";
			var secondsStr:String = DoubleDigitFormat(s);
			 
			return hourStr + minuteStr + secondsStr;
		}
 
		public static function DoubleDigitFormat(num:uint):String
		{
			if (num < 10) 
			{
				return ("0" + num);
			}
			return String(num);
		}
		
		public static function CapitolizeString(str:String) : String
		{
			var firstChar:String = str.substr(0, 1); 
			var restOfString:String = str.substr(1, str.length); 
			
			return firstChar.toUpperCase()+restOfString.toLowerCase(); 
		}
		
		// ^ Nice spelling, goof!
		public static function CapitalizeString(str:String) : String
		{
			return CapitolizeString(str);
		}
		
		public static function GetListString(listStrings:Array) : String
		{
			var andString:String = "&";
			var count:int = 0;
			
			var listString:String = "";
			for (var i = 0; i < listStrings.length; i++)
			{
				if (count > 0 && listStrings.length == 2) listString += " "+andString+" "; // For just 2 items
				else if (count == listStrings.length-1 && listStrings.length > 2) 
				{
					// For adding "and" at the end of 3+ items. It is: ", and $(item)"
					var endingString:String = ""; 
					endingString = endingString.replace("$(item)", listStrings[i]);
					listString += endingString;
					count++;
					continue;
				}
				else if (count > 0) listString += ", "; // Comma separated list
				
				listString += listStrings[i];
				count++;
			}
			
			return listString;
		}
		
		/*
		 * Flash doesn't like the way MySQL prints dates (2016-06-02 13:00:00)
		 * "The year month and day terms can be separated by a forward slash (/) or by spaces, but never by a dash (-)."
		 */
		public static function ParseDate (dateString:String) : Date
		{
			var pattern:RegExp = /-/g;
			var date = new Date();
			dateString = dateString.replace(pattern, "/");
			date.setTime(Date.parse(dateString));
			return date;
		}
		
		/*
		 * For display only, not intended to be read by ParseDate
		 * Tuesday, June 7th, 2016 at 10:32 AM
		 */
		public static function FormatDate (date:Date) : String
		{
			var f:DateTimeFormatter = new DateTimeFormatter("en-US");
			var dateStr:String;
			var lastChar:String;
			
			//Flash can't do "1st", "2nd", etc
			
			f.setDateTimePattern("d");
			dateStr = f.format(date);
			lastChar = dateStr.charAt(dateStr.length - 1);
			
			if (lastChar == "1" && dateStr != "11") dateStr += "st";
			else if (lastChar == "2" && dateStr != "12") dateStr += "nd";
			else if (lastChar == "3" && dateStr != "13") dateStr += "rd";
			else dateStr += "th";
			
			f.setDateTimePattern("EEEE, MMMM '" + dateStr + "', yyyy 'at' h:mm a");
			return f.format(date);
		}
		
		/* Simply counts the number of properties on the object
		 */
		public static function CountObjectParameters(obj:Object):int
		{
			var cnt:int=0;

			for (var s:String in obj) cnt++;

			return cnt;
		}
		
		/**
		 * For when shit gets real
		 */
		public static function PrintStackTrace()
		{
			try {
				throw new Error('StackTrace');
			} catch (e:Error) {
				_strace(e.getStackTrace());
			}
		}
	}
}