/*
VERSION:	7.4
	7.4		Change: 		loadNewCharset() checks for invalid file path by requiring a .png extension.  If invalid it aborts loading and resumes with the previous charset bitmap
				Fix: 				if getResolvedCharset() returns undefined,  then use the orignal charset path instead of undefined.
	7.3		Added: 			"unload" event is fired when sprite is being removed
	7.2		Fixed: 			Added  clearTimeout()  in every place where  _this.loopInterval =  is used.
	7.1		Change: 		loadNewCharset() will attempt to display using getResolvedCharset() if external code passed one into this sprite object.
	7.0		Fixed:			"animDone" event now fires after the last animation frame has been displayed for the correct length of time.  For "once" animations, it used to fire the instant the last frame was reached, without giving it time to be visible.
	6.9		Added: 			_this.update() fires an "update" event when _this.image_pic is altered.  (limited to firing once per frame at most)
	6.8		Fixed: 			setSpriteSize() was ignoring changes in frame-size when the total number of "directions" were altered
	6.7		Fixed: 			setSpriteSize() was being called twice very rapidly
										Initially,  a setTimeout() for loopSelf was getting set-up twice
										Sometimes a charset would load instantly and resolve the setParams vow prematurely before applyOtherSettings could occur
	6.6		Changed: 		frame delays are reset+paused while a chipset is loading
	6.5		Changed: 		Improved the reliability of "once" animation
	6.4		Added: 			_this.charset_pic_bk  backup of charset_pic while loading a new charset.
				Changed: 		onLoadError()  restores previous charset_pic from a backup  (charset_pic_bk)
	6.3		Removed: 		"allowFrameUpdates" flag  +  restored dispose() bitmap for reliable animation behavior
				Changed: 		"changeCharset" event distributes the new charset_pic
	6.2		Changed: 		charset_pic is not destroyed until a new charset is loaded  (so that external code always has a valid charset_pic to use)
				Changed: 		setSpriteSize()  detects changes in frame-size more reliably  (output bitmap changes size)
				Added: 			"allowFrameUpdates" flag to disable frame updates while the placeholder is displayed
	6.1		Added:			"onNewBitmap" event to announce when the display bitmap has been replaced
	6.0		Added:			is_sprite flag to allow external code to search for this
	5.9		Fixed:			checkDirection() was not enforcing max-direction when the input was a number
	5.8		Fixed:			setParams() was turning booleans into numbers.
	5.7		Added:			getDirectionValue() to convert "up" "right"  to corresponding number values
	5.6		Changed:		setParams() checks whether the cached image is valid by looking for .generateFilterRect
	5.5		Changed:		lookAt() lookAtSprite() lookAtCoords()  all return the new direction they result in, like so:		{direction:"up"}
	5.4		Changed getBitmap() to return a bitmap clone
	5.3		Added the "cache" parameter
	5.2		Added getBitmap()
	5.1		Added (!_this.addListener) check before initializing the AsBroadcaster
	5.0		Added "changePosition" event,  which fires  (up to once per frame)  if this sprite system's _x or _y have been changed
	4.9		Added "loadCharsetError" event
	4.8		Added getReadyPromise(), which returns a promise that resolves after the sprite finishes loading its first charset and all initial display settings have been applied
	4.7		Added getDisplayPosition(), which returns the coordinates used to position the current displayed image on top of the origin coordinate
	4.6		Added getDisplayBitmap(), which returns a reference to image_pic
	4.5		Added "changeDirection" event, which fires after setParams() if the "direction" setting was altered from its previous value
	4.4		setParams() returns a promise, which resolves after the charset & settings have finished changing
	4.3		loader_mc is scaled to 1% to avoid messing up external scroll-areas
	4.2		Added manualLoop() for manual looping  &  loopSelf() for automatic looping while useInterval = true
	4.1		Added "useInterval" flag to allow disabling setTimeout() looping for manual looping
	4.0		Removed "changeCharset" event-fire within setParams() when the charset has NOT been changed
	3.9		Updated documentation to explain image_pic as a publicly-used variable
	3.8		checks whether this movieClip has been removed before calling the next setTimeout()
	3.7		setParams now applies string values as numbers when possible
	3.6		Auto
	matically make loader_mc invisible while removing it
	3.5		Changed "once" looping behavior to allow stopping on the last frame, and re-animating from frame Zero
	3.4		Changed applyStopFrame() to only use 1 when there are exactly 3 frames
	3.3		Changed when isVisible() is used so invisible sprites can change charsets
	3.2		Replaced detectPoseBreak() with checkPose() because it was causing a glitch
	3.1		Added a frame-bounds check to "yoyo" animation-technique to handle 1-frame animations
	3.0		Added detectPoseBreak() to setParams() to check in case the pose setting is too high
	2.9		Added isVisible() check to avoid updates while hidden
	2.8		Shifted loader_mc and image_mc to reduce redraw-regions. setSpriteSize() and update() only run when a valid charset is loaded
	2.7		Added the "placeHolder_mc" snapshot to allow new settings to be applied immediately & prevent the resulting flicker while loading new charsets
	2.6		Added "changeCharset" event, which fires when setParams() finishes changing the charset
	2.5		Added "mask_mc" for legacy support
	2.4		Added "extra features"
	2.3		Encapsulated into a newSprite() function
	2.2		Added interface properties, methods, and events  (missing: extra features)
	2.1		Missing interface properties, methods, and events
	
NOTE:
	This is sprite system 4.
	It has been completely re-written from scratch to use BitmapData instead of masking
	
DESCRIPTION:
	Pros:		Performance.  Able to send BitmapData as a charset input.  (sprite system clones it)
	Cons:		Cannot load SWF files as charsets.  cannot display animated SWF files.
	
NOTE:
	This sprite system places the character's feet as the origin point.	(Feet:  1/2 image width, full image height)
	
	
USAGE:
	#include "functions/sprite.as"
	newSprite();			// minimum usage		(nothing is seen, but it can be modified later)
	
	
	#include "functions/sprite.as"
	var settings = {
		charset				:	"charset/marle.png",
		direction			:	2,				//(base zero)
		directions		:	4,				//(base one)		(amount of directions)
		frame					:	1,				//(base zero)
		frames				:	3,				//(base one)		(amount of frames)
		pose					:	0,				//(base zero)
		columns				:	4,				//(base zero)
		rows					:	2,				//(base zero)
		animType			:	"yoyo",
		animDirection	:	1,
		delay					:	3,				//(base one)
		isAnimating		:	true
	}
	mySprite = newSprite( [settings], [parent], [instanceName], [depth] );			// maximum usage		(rm2k settings)
	
	mySprite.setParams({isAnimating:true});
	
	spriteState = mySprite.getParams();
	
	direction = mySprite.getParam( "direction" );
	
	isUp_bool = mySprite.isFacing("up");
	
	// manual looping
	mySprite.useInterval = false;
	mySprite.onEnterFrame = mySprite.manualLoop;
	
	// re-enable auto-looping
	mySprite.useInterval = true;
	delete mySprite.onEnterFrame;
	mySprite.loopSelf();
	

PUBLIC VARIABLES:
	image_pic		This bitmap is the currently displayed animation frame.
		(When the charset changes, this gets DESTROYED and then REPLACED.  Use the "changeCharset" event to detect when this has happened)


EXTRA FEATURES:
	// Compares the PARENT movieClip's coords to the x,y passed to this function as parameters
	// This sprite must be wrapped within a movieClip. That movieClip's coords are then compared to the ones specified
	sprite.lookAtCoords( x, y );
	
	// make this sprite look at the specified movieClip.  (The specified movieClip must be a sibling of this sprite's parent)
	// This sprite must be wrapped within a movieClip. That movieClip's coords are then compared to specified movieClip
	sprite.lookAtSprite( movieClip );
	
	// DEPRECATED:		This makes the specified sprite1 look at the specified movieClip2.  (The specified movieClip must be a sibling of this sprite's parent)
	// The specified sprite1 must be wrapped within a movieClip. That movieClip's coords are then compared to specified movieClip2
	sprite.lookAt( sprite1, movieClip2 );
	
	// Report whether or not this sprite is facing in the specified direction
	// Directions can be specified either as numbers or strings:   "up" "right" "down" "left"
	_this.isFacing( checkDir )
	
	// Get a reference to the bitmap that displays the current animation frame.  (image_pic)
	_this.getDisplayBitmap()
	
	// Get the coordinates used to position the current animation frame so that their feet are standing on top of the origin  (make the character stand on top of 0,0 )
	_this.getDisplayPosition()
	
	// Get a promise which resolves after all of these finish:  initial chipset load, initial settings have been applied, and display has been drawn
	_this.getReadyPromise()
	
	
	
DEFAULT SETTINGS:
	You can skip most settings when you setup a sprite.
	When you do, it defaults to these values:
		charset				:	""		(can be set later)
		direction			:	0
		directions		:	1
		frame					:	0
		frames				:	1
		pose					:	0
		columns				:	1
		rows					:	1
		animType			:	"loop"
		animDirection	:	1
		delay					:	4
		isAnimating		:	true
	
DEPENDANCIES:
	nextDepth.as
	VOW.as

EVENTS:
	animDone						Broadcast after an animation completes or loops
	changeCharset				Broadcast after a new charset source-image has been loaded and set  +  distributes the new charset_pic as "bitmap"
	onNewBitmap					Broadcast when the output image_pic bitmap has been replaced  +  distributes the new image_pic as "bitmap"
	loadCharsetError		Broadcast when chipset file loading fails
	changeDirection			Broadcast after setParams() if the "direction" setting has been altered from its previous value
	changePosition			Broadcast when this sprite system gets moved by something  (origin _x or _y is different)
	update							{sprite, bitmap}		Broadcast when the image_pic image has been altered  (limited to firing once per frame at most)

DEPTHS:
	0		loader_mc
	1		image_mc
	2		placeHolder_mc
	3		mask_mc

INTERNAL VARIABLES
	var _this, internal, spriteWidth, spriteHeight, image_mc, image_pic, loader_mc, charset_pic, delayCounter, copy, paste, placeHolder_mc, placeHolder_pic;
INTERNAL FUNCTIONS
	var loadNewCharset, setSpriteSize, update, advanceFrame, onTimer, loop, manualLoop, loopSelf, showPlaceHolder, hidePlaceHolder, isVisible;

*/
import flash.display.BitmapData;
import flash.geom.Rectangle;
import flash.geom.Point;



function newSprite( newSettings, newTarget, newName, newDepth, cache )
{
	// resolve movieClip parameters
	// target
	var newTarget = (newTarget) ? newTarget : this;
	// name
	var newName = (newName) ? newName : "sprite_"+Math.floor(Math.random()*9999);
	while(newTarget[newName]){
		var newName = "sprite_"+Math.floor(Math.random()*9999);
	}// while:  this movieClip already exists
	// depth
	#include "nextDepth.as"
	#include "VOW.as"
	var newDepth = (newDepth!=undefined) ? newDepth : nextDepth(newTarget);
	
	
	// create movieClip
	var _this = newTarget.createEmptyMovieClip( newName, newDepth );
	if(!_this.addListener)		AsBroadcaster.initialize( _this );		// enable events
	_this.is_sprite = true;
	
	
	// internal auto-loop flag
	_this.useInterval = true;
	_this.loopInterval = undefined;
	_this.cache = cache;
	
	
	//////////////////////////////////////////////////////////////////////////////////////////////////
	// optionally use an externally provided getResolvedCharset() function
	function getResolvedCharset( charset ){
		if( _this.getResolvedCharset )		return _this.getResolvedCharset( charset ) || charset;
		return charset;
	}// getResolvedCharset()
	
	
	//////////////////////////////////////////////////////////////////////////////////////////////////
	// updateOnce()
	// copied from version 1.2
	function updateOnce( func, delay ){
		var hasUpdated = false;
		var interval = null;
		var delay = delay || 0;
		return function(){
			if(hasUpdated === false){
				// don't run again for awhile
				hasUpdated = true;
				
				// run the function
				func.apply( null, arguments );
				
				// wait before allowing it to be run again
				clearTimeout( interval );
				interval = setTimeout(function(){
					hasUpdated = false;
				}, delay);
			}// if:  has NOT run yet
		}// return new func()
	}// updateOnce()
	
	
	//////////////////////////////////////////////////////////////////////////////////////////////////
	// Able to send "update" event,  but only once per frame
	var send_update_event = updateOnce( function(){
		_this.broadcastMessage("update", {sprite: _this,  bitmap: _this.image_pic});
	}, 33 );// send_update_event()
	
	
	//////////////////////////////////////////////////////////////////////////////////////////////////
	// initialize variables
	_this.internal = {
		charset:"",
		direction:0,
		directions:1,
		frame:0,
		frames:1,
		pose:0,
		columns:1,
		rows:1,
		animType:"loop",
		animDirection:1,
		delay:4,
		isAnimating:true
	}// {internal}
	//for(var nam in newSettings)
	//	_this.internal[nam] = newSettings[nam];
	//
	_this.copy = new Rectangle(0,0, 16, 16);
	_this.paste = new Point(0,0);
	_this.delayCounter = 0;
	
	
	
	
	//////////////////////////////////////////////////////////////////////////////////////////////////
	// define functions
	_this.loadNewCharset = function( file, callback ){
		var success = false;
		var isUsingAutoLoop = _this.useInterval;
		var resolvedFile = getResolvedCharset( file );		// always a string or undefined.  Only used by things that expect only strings.  Otherwise "file" is used which may be a bitmap or a string.
		// manualLoop automatically detects whether or not a placeholder is present and pauses/un-pauses itself
		
		// check for invalid input  (if not a file,  then abort and re-use the previous charset bitmap)
		var inputIsBitmap = (file instanceof flash.display.BitmapData);
		var isValid = inputIsBitmap  ||  (resolvedFile.indexOf('.png') > -1);
		if( !isValid )		return callback( _this.charset_pic || null );
		
		// references the specified bitmapData object as charset_pic
		var copyBitmap = function( new_pic ){
			var oldCharset_pic = _this.charset_pic;
			_this.charset_pic = new_pic;
// 			_this.setSpriteSize();
			//
			_this.broadcastMessage("changeCharset", {bitmap:_this.charset_pic});
			_this.changeCharset( _this.charset_pic );
			callback( _this.charset_pic );
			oldCharset_pic.dispose();
			// _this.update();
			// enable animation-frame timing
			if( isUsingAutoLoop ){
				clearTimeout(_this.loopInterval);
				_this.loopInterval = setTimeout( function(){
					_this.loopSelf();
				}, getDelayMs(_this.internal.delay) );
			}
			// manualLoop automatically detects whether or not a placeholder is present and pauses/un-pauses itself
		}// copyBitmap()
		
		// takes a snapshot of loader_mc and stores it as charset_pic
		var copyMovieClip = function(){
			//_this.charset_pic.dispose();
			var oldCharset_pic = _this.charset_pic;
			_this.loader_mc._xscale = _this.loader_mc._yscale = 100;		// restore scale in order to read _width & _height
			_this.charset_pic = new BitmapData( _this.loader_mc._width, _this.loader_mc._height, true, 0 );
			_this.charset_pic.draw( _this.loader_mc );
			_this.loader_mc._xscale = _this.loader_mc._yscale = 1;		// stop taking up space, just in case removeMovieClip() takes awhile
			_this.loader_mc.removeMovieClip();
// 			_this.setSpriteSize();
			//
			_this.broadcastMessage("changeCharset", {bitmap:_this.charset_pic});
			_this.changeCharset( _this.charset_pic );
			callback( _this.charset_pic );
			oldCharset_pic.dispose();
			// _this.update();
			// enable animation-frame timing
			if( isUsingAutoLoop ){
				clearTimeout(_this.loopInterval);
				_this.loopInterval = setTimeout( function(){
					_this.loopSelf();
				}, getDelayMs(_this.internal.delay) );
			}
			// manualLoop automatically detects whether or not a placeholder is present and pauses/un-pauses itself
		}// copyMovieClip()
		
		
		// try direct bitmapData object
		if(file.generateFilterRect != undefined){
			copyBitmap( file.clone() );
			return;
		}// if:  input IS a bitmap
		
		// try internal bitmap
		var new_bitmap = BitmapData.loadBitmap( file );
		success = Boolean(new_bitmap.generateFilterRect !== undefined);
		if(success){
			copyBitmap( new_bitmap );
			return;
		}// if:  success
		
		// display a temporary snapshot while the new charset is being loaded
		_this.showPlaceHolder();
		// announce + distribute a clone of the output bitmap before destroying the charset bitmap
		_this.broadcastMessage("onNewBitmap", {bitmap: _this.image_pic.clone() });
		// 
		_this.charset_pic_bk = _this.charset_pic.clone();
		_this.charset_pic.dispose();		// somehow reliable,  but makes the main bitmap invalid for a moment
		// disable animation-frame timing until chipset is loaded
		if( isUsingAutoLoop ){
			clearTimeout( _this.loopInterval );
			_this.loopInterval = undefined;
		}
		// manualLoop automatically detects whether or not a placeholder is present and pauses/un-pauses itself
		
		
		// try internal movieClip
		_this.loader_mc = _this.attachMovie( resolvedFile, "loader_mc", 0, {_xscale:1,_yscale:1,_visible:false} );
		success = Boolean(_this.loader_mc != undefined);
		if(success){
			copyMovieClip();
			_this.hidePlaceHolder();
			// throw-away the backup
			_this.charset_pic_bk.dispose();
			_this.charset_pic_bk = null;
			return;
		}// if:  success
		
		// try external file
		_this.loader_mc = _this.createEmptyMovieClip("loader_mc", 0);
		_this.loader_mc._visible = false;
		//_this.loader_mc._x = -_this.spriteWidth/2;
		//_this.loader_mc._y = -_this.spriteHeight;
		_this.loader_mc._xscale = _this.loader_mc._yscale = 1;
		var loader = new MovieClipLoader();
		loader.onLoadComplete = function(){
			_this.loader_mc._visible = false;
		}// onLoadComplete()
		loader.onLoadInit = function(){
			copyMovieClip();
			_this.hidePlaceHolder();
			// throw-away the backup
			_this.charset_pic_bk.dispose();
			_this.charset_pic_bk = null;
			return;
		}// onLoadInit()
		loader.onLoadError = function(){
			_this.broadcastMessage("loadCharsetError");
			_this.loadCharsetError();
			// continue even if file-loading fails
			_this.hidePlaceHolder();
			// restore previous charset
			_this.charset_pic = _this.charset_pic_bk;
			_this.charset_pic_bk = null;
			// resume with the previous charset bitmap
			callback( _this.charset_pic || null );
			_this.update();
			if( isUsingAutoLoop ){
				clearTimeout(_this.loopInterval);
				_this.loopInterval = setTimeout( function(){
					_this.loopSelf();
				}, getDelayMs(_this.internal.delay) );
			}
			// manualLoop automatically detects whether or not a placeholder is present and pauses/un-pauses itself
		}
		loader.loadClip( resolvedFile, _this.loader_mc );
	}// loadNewCharset()
	
	
	/* Things that affect a frames's size
		
	*/
	_this.old_charset_pic;
	_this.setSpriteSize = function( doUpdate ){
		var hasUpdated = false;
		var old_spriteWidth = _this.spriteWidth;
		var old_spriteHeight = _this.spriteHeight;
		_this.spriteWidth = Math.floor(_this.charset_pic.width / _this.internal.columns / _this.internal.frames);
		_this.spriteHeight = Math.floor(_this.charset_pic.height / _this.internal.rows / _this.internal.directions);
		if(_this.spriteWidth>0)
		{// if:  there is a sprite to display
			if(	_this.old_charset_pic.width !== _this.charset_pic.width )		doUpdate = true;
			if(	_this.old_charset_pic.height !== _this.charset_pic.height )		doUpdate = true;
			if(	_this.spriteWidth !== old_spriteWidth )		doUpdate = true;
			if(	_this.spriteHeight !== old_spriteHeight )		doUpdate = true;
			if( doUpdate ){
				var oldImage_pic = _this.image_pic;
				_this.image_pic = new BitmapData( _this.spriteWidth, _this.spriteHeight, true, 0 );
				_this.image_mc = _this.createEmptyMovieClip("image_mc", 1);
				// stand on top of the origin
				var pos = _this.getDisplayPosition();
				_this.image_mc._x = pos.x;
				_this.image_mc._y = pos.y;
				//
				_this.image_mc.attachBitmap( _this.image_pic, 0 );
				//
				_this.copy.width = _this.spriteWidth;
				_this.copy.height = _this.spriteHeight;
				_this.update();		// redraw immediately to avoid flicker
				hasUpdated = true;		// sprite has been updated
				
				// legacy mask
				_this.updateMask();
				
				// announce the new bitmap
				_this.broadcastMessage("onNewBitmap", {bitmap: _this.image_pic});
				
				oldImage_pic.dispose();
			}// if:  sprite size changes
			
			_this.old_charset_pic = _this.charset_pic;
		}// if:  there is a sprite to display
		return hasUpdated;		// announce whether an update has been applied
	}// setSpriteSize()
	
	
	// copy from charset_pic -> image_pic
	_this.update = function(){
		if(_this.spriteWidth>0)
		{// if:  there is a sprite to display
			var xPoseIndex = _this.internal.pose %_this.internal.columns;
			var poseWidth = _this.spriteWidth *_this.internal.frames;
			var xPoseOffset = xPoseIndex *poseWidth;
			var frameOffset = _this.internal.frame *_this.spriteWidth;
			_this.copy.x = xPoseOffset + frameOffset;
			var yPoseIndex = Math.floor(_this.internal.pose /_this.internal.columns);
			var poseHeight = _this.spriteHeight *_this.internal.directions;
			var yPoseOffset = yPoseIndex *poseHeight;
			var directionOffset = _this.internal.direction *_this.spriteHeight;
			_this.copy.y = yPoseOffset + directionOffset;
			_this.image_pic.copyPixels( _this.charset_pic, _this.copy, _this.paste );
			// announce that the displayed image has (probably) changed
			send_update_event();
		}// if:  there is a sprite to display
	}// update()
	
	
	_this.advanceFrame = function(){
		// if:  a charset is being loaded,  then don't advance any animation until it is finished
		if( _this.placeHolder_pic )		return;
		
		if(_this.internal.frames > 1)
		{// if: there's more than 1 frame of animation
			_this.internal.frame += _this.internal.animDirection;
			
			// yoyo
			if(_this.internal.animType == "yoyo"){
				if(_this.internal.frame >= _this.internal.frames){
					_this.internal.frame = _this.internal.frames-1;
					_this.internal.animDirection *= -1;
					if(_this.internal.frame>0)
						_this.internal.frame += _this.internal.animDirection;
					// wait for this frame to be displayed for a full fps duration,  then announce this animation is done
					// callOnNextFrame( function(){
						_this.broadcastMessage("animDone");
						_this.animDone();
					// } );
				}else if(_this.internal.frame <= -1){
					_this.internal.frame = 0;
					_this.internal.animDirection *= -1;
					if(_this.internal.frame < _this.internal.frames-1)
						_this.internal.frame += _this.internal.animDirection;
					// wait for this frame to be displayed for a full fps duration,  then announce this animation is done
					// callOnNextFrame( function(){
						_this.broadcastMessage("animDone");
						_this.animDone();
					// } );
				}
			}// if:  yoyo
			
			// loop
			else if(_this.internal.animType == "loop"){
				var oldFrame = _this.internal.frame;
				
				// loop relative to how far the max frames were exceeded
				// loop beyond minimum
				while(_this.internal.frame < 0)
					_this.internal.frame += _this.internal.frames;
				// loop beyond maximum
				_this.internal.frame %= _this.internal.frames;
				
				if(oldFrame >= _this.internal.frames){
					//_this.internal.frame = 0;
					// wait for this frame to be displayed for a full fps duration,  then announce this animation is done
					// callOnNextFrame( function(){
						_this.broadcastMessage("animDone");
						_this.animDone();
					// } );
				}else if(oldFrame <= -1){
					//_this.internal.frame = _this.internal.frames-1;
					// wait for this frame to be displayed for a full fps duration,  then announce this animation is done
					// callOnNextFrame( function(){
						_this.broadcastMessage("animDone");
						_this.animDone();
					// } );
				}
			}// if:  loop
			
			// once
			else if(_this.internal.animType == "once"){
				if(_this.internal.frame >= _this.internal.frames)
				{// if:  anim restart caused the current frame to advance forward out-of-bounds,  then start-over and continue animating  (if animation is turned on)
					// loop the next time the when animation is restarted
					_this.internal.frame = 0;
				}else if(_this.internal.frame <= -1)
				{// if:  anim restart caused the current frame to advance backward out-of-bounds,  then start-over and continue animating  (if animation is turned on)
					_this.internal.frame = _this.internal.frames-1;
				}else if(_this.internal.animDirection > 0  &&  _this.internal.frame == _this.internal.frames-1)
				{// if:  animating forwards  +  animation hits the last frame,  then stop animating
					// stop when final (last) frame is hit
					_this.internal.isAnimating = false;
					// wait for this frame to be displayed for a full fps duration,  then announce this animation is done
					callOnNextFrame( function(){
						_this.broadcastMessage("animDone");
						_this.animDone();
					} );
				}else if(_this.internal.animDirection < 0  &&  _this.internal.frame == 0)
				{// if:  animating backwards  +  animation hits the first frame,  then stop animating
					// stop when final (first) frame is hit
					_this.internal.isAnimating = false;
					// wait for this frame to be displayed for a full fps duration,  then announce this animation is done
					callOnNextFrame( function(){
						_this.broadcastMessage("animDone");
						_this.animDone();
					} );
				}
			}// if:  once
		}// if: there's more than 1 frame of animation
		else
		{// if:  there's only 1 frame of animation
			_this.internal.frame = 0;
		}// if:  there's only 1 frame of animation
	}// advanceFrame()
	
	
	_this.getDisplayPosition = function(){
		var output = {
			x: -_this.spriteWidth / 2,
			y: -_this.spriteHeight
		};
		return output;
	}// getDisplayPosition()
	
	
	
	//////////////////////////////////////////////////////////////////////////////////////////////////
	// Interface
	_this.getParams = function(){
		var output = {};
		for(var nam in _this.internal)
			output[nam] = _this.internal[nam];
		return output;
	}// getParams()
	
	
	_this.getParam = function( nam ){
		return _this.internal[nam];
	}// getParam()
	
	
	_this.setParams = function( new_settings )
	{
		var update_vow = VOW.make();
		var detectStopAnim = function(){
			if(	_this.internal.isAnimating === true
			&&	new_settings.isAnimating === false){
				applyStopFrame();
			}// if:  animation is being stopped
		}// detectStopAnim()
		
		var checkDirection = function(){
			var maxDirections = (new_settings.directions !== undefined)		?  new_settings.directions  :  _this.internal.directions;
			_this.internal.direction = _this.getDirectionValue( _this.internal.direction, maxDirections );
		}// checkDirection()
		
		var checkDelay = function(){
			if(_this.internal.delay <= 0)
				_this.internal.delay = 1;
		}// checkDelay()
		
		var checkPose = function(){
			var maxPose = _this.internal.columns *_this.internal.rows;
			if(_this.internal.pose >= maxPose)
				_this.internal.pose = 0;
		}// checkPose()
		
		var checkFrame = function(){
			// adjust frames
			// if:  old frame is now out-of-bounds
			if(_this.internal.frame >= _this.internal.frames){
				applyStopFrame();
			}// if:  old frame is now out-of-bounds
		}// checkFrame()
		
		var applyStopFrame = function(){
			if(_this.internal.frames == 3){
				// 3 frames
				_this.internal.frame = 1;
			}else{
				// 1, 2, or many frames
				_this.internal.frame = 0;
			}
		}// applyStopFrame()
		
		
		
		var applyOtherSettings = function(){
			// a new charset may still be loading when applyOtherSettings() runs.  This can be detected by checking if a placeholder is present  (_this.placeHolder_pic)
			// detectInvalidPose();		// not used
			detectStopAnim();
			
			// detect direction change  (part 1 of 2)
			var dir_old = _this.internal.direction;
			
			// copy settings
			for(var nam in new_settings){
				var newValue = new_settings[nam];
				var isBoolean = newValue === true  ||  newValue === false;
				var isNumber = Boolean(!isNaN(newValue))  &&  !isBoolean;		//  if it's a boolean,  then it's not a number
				if(isNumber)
					_this.internal[nam] = Number(newValue);
				else
					_this.internal[nam] = newValue;
			}// for:  each new setting
			
			checkDirection();
			checkFrame();
			checkDelay();
			checkPose();
			
			
			// immediately redraw
			/*	
			if( !_this.placeHolder_pic ){
				var hasUpdated = _this.setSpriteSize();
				if(hasUpdated)	_this.update();
			}
			*/
 			// if( !_this.placeHolder_pic )		_this.update();		// update() would be useless & incorrect if the chipset is still being loaded
			
			
			// detect direction change  (part 2 of 2)
			var dir_new = _this.internal.direction;
			var directionHasChanged = (dir_old !== dir_new);
			if(directionHasChanged){
				_this.broadcastMessage("changeDirection", {oldValue:dir_old, newValue:dir_new, total:_this.internal.directions});
			}
		}// applyOtherSettings()
		
		
		var charsetChanged = (new_settings.charset != _this.internal.charset);
		if(new_settings.charset  &&  charsetChanged){
			var hasCachedVersion = Boolean(_this.cache[new_settings.charset].generateFilterRect !== undefined);
			if( !_this.cache[new_settings.charset].width )		hasCachedVersion = false;
			var clonedBitmap = _this.cache[new_settings.charset].clone();
			var loadThis = (hasCachedVersion) ? clonedBitmap : new_settings.charset;
			
			var charsetLoaded = function( newCharset_pic ){
				// _this.broadcastMessage("changeCharset", {bitmap:_this.charset_pic});
				// _this.changeCharset( _this.charset_pic );
				// _this.changeCharset( newCharset_pic );
				// immediately redraw
				var hasUpdated = _this.setSpriteSize();
				// if(!hasUpdated)	_this.update();
				if( !_this.placeHolder_pic )		_this.update();		// update() would be useless & incorrect if the chipset is still being loaded
				
				update_vow.keep( new_settings );
			}// charsetLoaded()
			
			applyOtherSettings();		// no longer  calls update()
			_this.loadNewCharset( loadThis, charsetLoaded );// after loading charset
		}
		else{
			applyOtherSettings();
			// why is this broadcastMessage here??
			// _this.broadcastMessage("changeCharset", {bitmap:_this.charset_pic});
			// _this.changeCharset( _this.charset_pic );
			
			// immediately redraw
			var hasUpdated = _this.setSpriteSize();
			// if(!hasUpdated)	_this.update();
			if( !_this.placeHolder_pic )		_this.update();		// update() would be useless & incorrect if the chipset is still being loaded
			
			update_vow.keep( new_settings );
		}
		return update_vow.promise;
	}// setParams()
	
	
	_this.showPlaceHolder = function(){
		// no current image  =  no placeholder
		// var placeHolderIsPossible = Boolean( _this.spriteWidth>0 );
		var placeHolderIsPossible = Boolean( _this.image_pic.width>0 );
		if( !placeHolderIsPossible )		return false;
		
		var oldPlaceholder_pic = _this.placeHolder_pic;
		_this.placeHolder_mc = _this.createEmptyMovieClip("placeHolder_mc", 2);
		// stand on top of the origin
		_this.placeHolder_mc._x = -_this.spriteWidth / 2;
		_this.placeHolder_mc._y = -_this.spriteHeight;
		//_this.update();
		_this.placeHolder_pic = _this.image_pic.clone();
		_this.placeHolder_mc.attachBitmap( _this.placeHolder_pic, 0 );
		_this.image_mc._visible = false;
		oldPlaceholder_pic.dispose();
		// 
		return true;
	}// showPlaceHolder()
	
	_this.hidePlaceHolder = function(){
		var oldPlaceholder_pic = _this.placeHolder_pic;
		_this.placeHolder_mc.removeMovieClip();
		_this.image_mc._visible = true;
		oldPlaceholder_pic.dispose();
		delete _this.placeHolder_pic;
		// 
		return true;
	}// hidePlaceHolder()
	
	
	_this.isVisible = function(){
		var visibility = _this._visible && _this._parent._visible;
		var opacity = _this._alpha>0 && _this._parent._alpha>0;
		return visibility && opacity;
	}// isVisible()
	
	
	var detectChangePosition = make_detectChangePosition();
	function make_detectChangePosition(){
		var old_pos = {
			x: _this._x,
			y: _this._y
		}
		
		return function(){
			var new_pos = {
				x: _this._x,
				y: _this._y
			}
			if(new_pos.x !== old_pos.x  ||  new_pos.y !== old_pos.y){
				_this.broadcastMessage("changePosition", {oldValue:old_pos, newValue:new_pos});
				old_pos.x = new_pos.x;
				old_pos.y = new_pos.y;
				return true;
			}// if:  this sprite system has been moved
			return false;
		}// detectChangePosition()
	}// make_detectChangePosition()
	
	
	
	//////////////////////////////////////////////////////////////////////////////////////////////////
	// EXTRA FEATURES
	
	// The sprite needs to be inside of a movieClip for lookAtCoords() to work
	// Compares the parent movieClip's coords to the x,y passed to this function as parameters
	_this.lookAtCoords = function()
	{
		var newSetting = {direction:_this.getParam("direction")};
		var destX = arguments[0];
		var destY = arguments[1];
		// get a unit vector
		var xDiff = destX - _this._parent._x;
		var yDiff = destY - _this._parent._y;
		
		// compare x & y
		if( Math.abs(yDiff) > Math.abs(xDiff) )
		{
			// vert
			if( yDiff < 0)
			{
				// up
				newSetting = {direction:"up"};
				_this.setParams( newSetting );
			}else{
				// down
				newSetting = {direction:"down"};
				_this.setParams( newSetting );
			}// if:  up or down
		}else{
			// horz
			if( xDiff < 0 )
			{
				// left
				newSetting = {direction:"left"};
				_this.setParams( newSetting );
			}else{
				// right
				newSetting = {direction:"right"};
				_this.setParams( newSetting );
			}// if:  left or right
		}// if:  vert or horz
		return newSetting;
	}// lookAtCoords()
	
	
	// make this sprite look at the specified movieClip.  (The specified movieClip must be a sibling of this sprite's parent)
	_this.lookAtSprite = function( clip2 ){
		return _this.lookAtCoords( clip2._x, clip2._y );
	}// lookAtSprite()
	
	
	/*
	// DEPRECATED:		This makes the specified sprite look at the specified movieClip.  (The specified movieClip must be a sibling of this sprite's parent)
	_this.lookAt = function( sprite1, clip2 ){
		sprite1.lookAtSprite( clip2 );
	}// lookAt()
	*/
	_this.lookAt = function( sprite1, sprite2 )
	{
		var newSetting = {direction:_this.getParam("direction")};
		// get a unit vector
		var xDiff = sprite2._x - sprite1._x;
		var yDiff = sprite2._y - sprite1._y;
		var dist = Math.sqrt( xDiff*xDiff + yDiff*yDiff );
		var xUnit = xDiff / dist;
		var yUnit = yDiff / dist;
		
		// compare x & y
		if( Math.abs(yUnit) > Math.abs(xUnit) )
		{
			// vert
			if( yUnit < 0)
			{
				// up
				newSetting = {direction:"up"};
				_this.setParams( newSetting );
			}else{
				// down
				newSetting = {direction:"down"};
				_this.setParams( newSetting );
			}// if:  up or down
		}else{
			// horz
			if( xUnit < 0 )
			{
				// left
				newSetting = {direction:"left"};
				_this.setParams( newSetting );
			}else{
				// right
				newSetting = {direction:"right"};
				_this.setParams( newSetting );
			}// if:  left or right
		}// if:  vert or horz
		return newSetting;
	}// lookAt()
	
	
	
	// report whether or not this sprite is facing in the specified direction
	_this.isFacing = function( checkDir ){
		var checkDir = _this.getDirectionValue( checkDir, _this.internal.directions );
		return (_this.internal.direction == checkDir);
	}// isFacing()
	
	
	
	// grant access to the bitmap which displays this sprite's current animation frame
	_this.getDisplayBitmap = function(){
		return _this.image_pic;
	}// getDisplayBitmap()
	
	
	
	_this.getReadyPromise = function(){
		return _this.ready_prom;
	}// getReadyPromise()
	
	
	
	_this.getBitmap = function(){
		return _this.charset_pic.clone();
	}// getBitmap()
	
	
	
	_this.getDirectionValue = function( input, totalDirections ){
		if(totalDirections === undefined)		totalDirections = Number( _this.internal.directions ) || 1;		// must be at least 1  +  must be a number
		if( typeof(input) === "string"  &&  !isNaN(input) )		return Number(input);
		var output = Number( input ) || 0;
		
		if( typeof(input) === "string" ){
			output = input.toLowerCase();
			var incr = totalDirections/4;
			if(output=="up"){
				output = 0;
			}else if(output=="right"){
				output = Math.floor(1*incr);
			}else if(output=="down"){
				output = Math.floor(2*incr);
			}else if(output=="left"){
				output = Math.floor(3*incr);
			}
			output = Number( output );		// garauntee that "output" is always a number,  so that the "while" statement doesn't malfunction
		}
		// loop relative to how far the bounds were exceeded
		// limit minimum to looping range
		while(output < 0)
			output += totalDirections;
		// loop the maximum
		output %= totalDirections;
		
		return output;
	}// getDirectionValue()
	
	
	
	//////////////////////////////////////////////////////////////////////////////////////////////////
	// Legacy features
	_this.set_charset = function( new_charset, newThis, callback ){
		_this.loadNewCharset( new_charset, callback );
	}// set_charset()
	
	
	_this.set_direction = function( new_dir ){
		_this.setParams({direction:new_dir});
	}// set_direction
	
	
	// create mask
	_this.mask_mc = _this.createEmptyMovieClip( "mask_mc", 3 );
	_this.mask_mc.beginFill(0xff0000, 0);
	_this.mask_mc.lineTo(16,0);
	_this.mask_mc.lineTo(16,16);
	_this.mask_mc.lineTo(0,16);
	_this.mask_mc.lineTo(0,0);
	_this.mask_mc.endFill();
	_this.updateMask = function(){
		// position
		_this.mask_mc._x = -_this.spriteWidth / 2;
		_this.mask_mc._y = -_this.spriteHeight;
		// size
		_this.mask_mc._width = _this.spriteWidth;
		_this.mask_mc._height = _this.spriteHeight;
	}// updateMask();
	
	
	
	
	//////////////////////////////////////////////////////////////////////////////////////////////////
	// LOOP
	var isFirstTime = true;
	_this.fps = 30;
	_this.loop = function(){
		if(_this.internal.isAnimating  &&  _this.isVisible() ){
			if( isFirstTime === false )		_this.advanceFrame();		// don't advance when displaying a frame for the first time
			_this.update();
		}// isAnimating()
		detectChangePosition();		// if the position has changed,  fire a "changePosition" event
		isFirstTime = false;
	}// loop()
	_this.loop();
	
	
	_this.loopSelf = function()
	{
		if( _this.useInterval !== true )		return;
		
		var thisExists = (_this._name != "");
		if(thisExists)
		{// if:  self exists
			_this.loop();
			clearTimeout(_this.loopInterval);
			_this.loopInterval = setTimeout( function(){
				_this.loopSelf();
			}, getDelayMs(_this.internal.delay) );
		}// if:  self exists
		else
		{// if:  self does NOT exist
			_this.loopSelf = null;
			delete _this.loopSelf;
		}// if:  self does NOT exist
	}// loopSelf()
	
	// setParams() will always initially run anyway,  and it will set up this setTimeout after loading a charset
	// _this.loopInterval = setTimeout( function(){
		// _this.loopSelf();
	// }, 0 );
	
	
	
	function getDelayMs( delay ){
		if( delay === undefined )		delay = _this.internal.delay;
		return Math.floor(1000 / _this.fps * delay);
	}// getDelayMs()
	
	
	
	_this.manualLoop = makeManualLoop();
	function makeManualLoop(){
		var delayCounter = 0;
		return function(){
			// if a new charset is loading,  then halt all frame-delay counting + reset delay to start-over
			if( _this.placeHolder_pic ){
				delayCounter = _this.internal.delay;
				return;
			}
			// else
			if(delayCounter <= 0){
				_this.loop();
				delayCounter = _this.internal.delay;
			}// if:  delay has run out
			delayCounter--;
		}// return: manualLoop()
	}// makeManualLoop()
	
	
	
	// run code after 1 full frame delay has elapsed
	function callOnNextFrame( callThis ){
		setTimeout( function(){
			callThis();
		}, getDelayMs(_this.internal.delay) );
	}// callOnNextFrame()
	
	
	
	//////////////////////////////////////////////////////////////////////////////////////////////////
	// initial setup
	//_this.loadNewCharset( _this.internal.charset );
	_this.ready_prom = _this.setParams( newSettings );
	
	
	
	//////////////////////////////////////////////////////////////////////////////////////////////////
	// clean-up
	_this.onUnload = function(){
		if(_this.loopInterval!=undefined){
			clearTimeout( _this.loopInterval );
			_this.loopInterval = undefined;
		}
		// announce unload
		_this.broadcastMessage("unload");
		// clear events
		for(var i in _this._listeners)
			_this._listeners[i] = null;
		_this._listeners = [];
	}// onUnload()

	
	
	//////////////////////////////////////////////////////////////////////////////////////////////////
	return _this;
}// newSprite()
