var version = 3.2;
/*
VERSION:	3.2
	3.2		Change:  made scroll-panels optional.  default is false.  by adding "useScrollPanels" option to drawMap()  (this makes it more compatible with the GameEditor's map editor)
	3.1		Change:  drawMap() now crops the map to the intended "full" size. (excluding the redundant scroll-panels)
	3.0		added scroll-panels inside each layer
	2.3		scroll() returns _this.scrollArea rectangle which contains the view-size & current x,y scroll position (top-left corner of the scroll rectangle, relative to the top-left corner of the map)
	2.2		Fixed scroll() to use map's tileSize
	2.1		Fixed black-boxes by adding forceRedraw() to scroll().  forceRedraw() only runs 2 times.
	2.0		Fixed scrolling to include extreme edges for maps that scroll
	1.9		Fixed compression & de-compression code
	1.8		Added:  Now checks if a new map has started loading while a previous map was still loading, and aborts the previous map-load
				Change:  All errors return an error object  {type, message}		type is either "fail" or "abort"
	1.7		Fixed glitch in isCompressed()
	1.6		removed redundant width & height parameters from makeMap()
	1.5		useCache defaults to true
	1.4		Added useCache to drawMap() so it can optionally be forced to reload all chipsets
	1.3		Added checks to decompressMapData() & compressMapData() to avoid accidentally attempting to compress already-compressed data,  and avoid decompressing already-decompressed data.  Suitable data simply passes through.
	1.2		Removed legacy properties:  collision_array, width, height, tileSize  because "map_formats.as" adds them instead.
				Modified scroll() so that it no longer relies on legacy width & height properties.
	1.1		Added scroll()  &  x,y properties


DEPENDENCIES:
	copyObject.as
	VOW.as
	VOW/loadBitmap.as
		VOW/loadBitmap/loadImage.as
		VOW/loadBitmap/nextDepth.as
	nextDepth.as
	
	
USAGE:
	// Displaying a map.
		#include "functions/map3.as"
		map_mc = makeMap();
		map_mc.drawMap( myMapData, true );
		
		
	// Modifying a map tile.
		var tileData = mapData.layers[0][0][1] = {
			chipset: 1,
			x: 6,
			y: 0
		}
		var updateLocation = {
			layer: 0,
			x: 0,
			y: 1
		}
		map_mc.updateTile( updateLocation, tileData, newData.tileSize );
		
		
	// Using isCompressed() to detect whether map data needs to be de-compressed.
		if( !isCompressed( mapData )  )
		{// if:  data is compressed
			uncompressedMapData = decompressMapData( uncompressedMapData );
			mapData = uncompressedMapData;
		}// if:  data is compressed
		
		
	// Compressing map data for efficient storage / text transfer.
		compressedMapData = compressMapData( uncompressedMapData );
		
		
	// De-compressing map data for convenient editing.
		decompressed = decompressMapData( compressed );
		
		
	// Scrolling the map.
		onEnterFrame = function(){
			map_mc.scroll( 100, 64, 64, 64, 0.05);
		}// loop()


PROPERTIES:
	x,y										The scroll position of the map.  Use these instead of _x & _y.  These behave like _x & _y  (but the map's movieClip size is locked to screen-size)

	
FUNCTIONS:
	drawMap()							[promise]		Updates the displayed tiles based on provided mapData.  (removes extra layers,  resizes map,  and loads chipset files as needed)
	updateTile()					[null]			Updates a specified tile using the specified tile-data.  (does not create new layers)
	isCompressed()				[t/f]				Checks provided mapData and reports whether or not it is compressed.
	compressMapData()			[compressedMapData]			Receives uncompressed data and returns compressed data suitable for efficient saving.  (can be used as a static function)
	decompressMapData()		[uncompressedMapData]		Receives compressed data and returns decompressed data suitable for editing.  (can be used as a static function)
	scroll()							[null]			Scrolls the map using scrollRect
	
	
FUNCTION PARAMS
	movieClip = 						makeMap( [instanceName], [target_mc], [depth], [width], [height] )
	null =									updateTile( location, tileData, [tileSize] )
	t/f =										isCompressed( mapData )
	compressedMapData =			compressMapData( uncompressedMapData )
	uncompressedMapData =		decompressMapData( compressedMapData )
	
	
INTERNAL DEPTHS:
	0				map layer  (first)
	10			map layer
	20			map layer
	...			
	350			map layer  (max)
	2001		chipsetLoader_mc
	
	Layers are assigned depths in incriments of 10
	This allows movieClips to be inserted between them.
	
	
NOTE:
	chipsets can be either external files, or bitmaps with linkage.
*/
function makeMap( instanceName, target_mc, depth )
{
	////////////////////////////////////////////////////////////////////
	// DEPENDENCIES
	
	#include "copyObject.as"
	#include "VOW.as"
	#include "VOW/loadBitmap.as"
		// VOW/loadBitmap/loadImage.as
		// VOW/loadBitmap/nextDepth.as
	#include "nextDepth.as"
	
	
	
	////////////////////////////////////////////////////////////////////
	// ONE-TIME INIT
	
	// static settings
	var layerDepthOffset = 10;
	var encodingRadix = 36;		// base-36 is the maximum that Flash allows
	
	// create container movieClip
	var target_mc = target_mc || this;
	var depth = (depth != undefined) ? depth : nextDepth(target_mc);
	var instanceName = instanceName || "map"+depth;
	var _this = target_mc.createEmptyMovieClip( instanceName, depth );
	
	// movieClip structure
	_this.version = version;
	_this.layers_mc_array = [];
	_this.chipset_array = [];
	
	// internal shortcuts
	//var data = _this.data;
	var chipsetLoader_mc = _this.createEmptyMovieClip( "chipsetLoader_mc", 2001 );
	chipsetLoader_mc._visible = false;
	var layers_mc_array = _this.layers_mc_array;
	var chipset_array = _this.chipset_array;
	var oldTilesize = _this.tileSize;
	var BitmapData = flash.display.BitmapData;
	var Rectangle = flash.geom.Rectangle;
	var Point = flash.geom.Point;
	var currentChipsetLoad = {};		// used to detect when new map-loads interrupt previous map-loading
	currentChipsetLoad.chipsetLoad_prom = null;
	
	
	
	////////////////////////////////////////////////////////////////////
	// drawMap()
	
	function drawMap( uncompressedMapData, useCache, useScrollPanels )
	{
		if(useCache == undefined)						var useCache=12;
		if(useScrollPanels === undefined)		var useScrollPanels = false;
		var drawMap_vow = VOW.make();
		if( isCompressed( uncompressedMapData ) === true ){
			uncompressedMapData = decompressMapData( uncompressedMapData );
		}// if:  data is compressed
		var newData = uncompressedMapData;
		
		// Immediately crop everything to the intended size  (waiting could cause mistakes if other things try to detect the size of this map)
		cropMap();
		
		// break down
		removeExtraLayers();
		resizeExistingLayers();
		// build up
		addNewLayers();
		loadNewChipsets()
		.then( drawAllTiles, drawMapError )
		.then( finished, drawMapError );
		
		
		
		function cropMap(){
			var pixWidth = newData.width * (newData.tileSize || 16);
			var pixHeight = newData.height * (newData.tileSize || 16);
			_this.scrollRect = new flash.geom.Rectangle( 0,0, pixWidth,pixHeight );
		}// cropMap()
		
		
		
		function removeExtraLayers()
		{
			// compare known layers against the map-newData
			for(var l=layers_mc_array.length; l>=0; l--)
			{// for:  each existing layer
				var newDataLayer = newData.layers[l];
				if(newDataLayer == undefined)
				{// if:  the newData doesn't have this layer
					// remove this layer from the movieClip
					layers_mc_array[l].pic.dispose();				// delete layer's bitmapData
					layers_mc_array[l].removeMovieClip();		// remove layer's movieClip
					layers_mc_array.splice( l, 1 );					// forget about this layer
				}// if:  the newData doesn't have this layer
			}// for:  each existing layer
		}// removeExtraLayers()
		
		
		function resizeExistingLayers()
		{
			var oldWidth = layers_mc_array[0].pic.width;
			var oldHeight = layers_mc_array[0].pic.height;
			if( (oldWidth != undefined)  &&  (oldTilesize != newData.tileSize  ||  oldWidth != newData.width || oldHeight != newData.height) )
			{// if:  width OR height differ from current bitmaps
				var newWidth = newData.width * newData.tileSize;
				var newHeight = newData.height * newData.tileSize;
				for(var l=layers_mc_array.length; l>=0; l--)
				{// for:  each existing layer
					var thisLayer_mc = layers_mc_array[l];
					thisLayer_mc.pic.dispose();
					thisLayer_mc.pic = new BitmapData( newWidth, newHeight, true, 0 );
					
					for(var p=0; p<thisLayer_mc.panels.length; p++){
						thisLayer_mc.panels[ p ].attachBitmap( thisLayer_mc.pic, 0 );
					}
					// thisLayer_mc.attachBitmap( thisLayer_mc.pic, 0 );
				}// for:  each existing layer
			}// if:  width OR height differ from current bitmaps
			oldTilesize = newData.tileSize;		// remember last-used tileSize for future comparisons
		}// resizeExistingLayers()
		
		
		
		function addNewLayers()
		{
			var pixelWidth = newData.width * (newData.tileSize || 16);
			var pixelHeight = newData.height * (newData.tileSize || 16);
			// start at the next layer up from the current amount of layers that have been drawn,  then check if this is less than the total number of layers in the data  (AKA append additional layers if there are more in the data)
			for(var l=layers_mc_array.length; l<newData.layers.length; l++ ){
				// create a layer
				var newDepth = l * layerDepthOffset;
				var newName = "layer"+l+"_mc";		//->  layer0_mc
				var newLayer_mc = _this.createEmptyMovieClip( newName, newDepth );
				layers_mc_array.push( newLayer_mc );
				
				newLayer_mc.pic = new BitmapData( pixelWidth, pixelHeight, true, 0 );
				// create scroll-panel movieClips,  attach bitmaps to each of them,  remember the panels
				newLayer_mc.panels = [];
				if(useScrollPanels)		newLayer_mc.panels.push(	addPanel( "topLeft",			{x:-pixelWidth,	y:-pixelHeight}	)		);
				if(useScrollPanels)		newLayer_mc.panels.push(	addPanel( "top",					{x:0,						y:-pixelHeight}	)		);
				if(useScrollPanels)		newLayer_mc.panels.push(	addPanel( "topRight",			{x:pixelWidth,	y:-pixelHeight}	)		);
				if(useScrollPanels)		newLayer_mc.panels.push(	addPanel( "left",					{x:-pixelWidth,	y:0}						)		);
				newLayer_mc.panels.push(	addPanel( "middle",				{x:0,						y:0}						)		);
				if(useScrollPanels)		newLayer_mc.panels.push(	addPanel( "right",				{x:pixelWidth,	y:0}						)		);
				if(useScrollPanels)		newLayer_mc.panels.push(	addPanel( "bottomLeft",		{x:-pixelWidth,	y:pixelHeight}	)		);
				if(useScrollPanels)		newLayer_mc.panels.push(	addPanel( "bottom",				{x:0,						y:pixelHeight}	)		);
				if(useScrollPanels)		newLayer_mc.panels.push(	addPanel( "bottomRight",	{x:pixelWidth,	y:pixelHeight}	)		);
			}// for:  each layer to add
				
				
			function addPanel( newName, offset_p ){
				var newDepth = nextDepth( newLayer_mc );
				var newPanel = newLayer_mc.createEmptyMovieClip( newName, newDepth );
				newPanel._x = offset_p.x;
				newPanel._y = offset_p.y;
				newPanel.attachBitmap( newLayer_mc.pic, 0 );
				newPanel.cacheAsBitmap = true;
				return newPanel;
			}// addPanel()
		}// addNewLayers()
		
		
		function loadNewChipsets()
		{
			var chipsetLoad_prom = null;
			var loadAllChipsets_vow = VOW.make();
			// compare old chipset newData to new chipset newData
			// // remember all chipsets that already been loaded
			var allLoadedChipsets = {};
			var chipsetsToLoad = [];
			if(useCache)
			{// if:  re-use previously loaded chipsets
				for(var c=0; c<chipset_array.length; c++)
				{// for:  each existing chipset
					allLoadedChipsets[ chipset_array[c].file ] = chipset_array[c].pic;
				}// for:  each existing chipset
			}// if:  re-use previously loaded chipsets
			// // make a list of new chipsets that haven't already been loaded
			for(var c=0; c<newData.chipsets.length; c++)
			{// for:  each new chipset
				var newChipName = newData.chipsets[c];
				if(allLoadedChipsets[newChipName] == undefined)
				{// if:  this chipset has NOT been loaded yet
					chipsetsToLoad.push( newChipName );
				}// if:  this chipset has NOT been loaded yet
			}// for:  each new chipset
			// // load the remaining chipsets
			if(chipsetsToLoad.length > 0)
			{// if:  there are more chipsets to load
				var load_array = [];
				for(var l=0; l<chipsetsToLoad.length; l++)
				{// for:  each missing chipset
					// load & keep track of each chipset
					var load_promise = loadChipset( chipsetsToLoad[l] );
					load_array.push( load_promise );
				}// for:  each missing chipset
				// wait for all chipsets to finish loading
				currentChipsetLoad.chipsetLoad_prom = chipsetLoad_prom = VOW.every( load_array );		// store a global reference & a local reference, for comparison to check whether a new map-load has interrupted this one
				chipsetLoad_prom.then( checkForAbortedLoad )
				.then( afterAllChipsetsLoad, loadChipsetError );
			}// if:  there are more chipsets to load
			else
			{// if:  no additional chipsets needed
				// arrange chipsets, old and new, into the order specified by the new newData
				arrangeChipsets();
				// report success because no loading is needed
				loadAllChipsets_vow.keep();
			}// if:  no additional chipsets needed
			
			
			function loadChipset( fileName )
			{
				var vow = VOW.make();
				// Load the new image
				var load_promise = loadBitmap( fileName, chipsetLoader_mc );
				load_promise.then( afterLoading, error );
				
				// If successful...
				function afterLoading( newChipset_pic ){
					var chipsetData = {
						file: fileName,
						pic: newChipset_pic
					}
					vow.keep( chipsetData );
				}// afterLoading()
				function error( message ){
					vow.doBreak( {
						type: "fail",
						message: message
					} );
				}// error()
				
				return vow.promise;
			}// loadChipset()
			
			
			
			function checkForAbortedLoad( allNewChipsets ){
				var vow = VOW.make();
				var loadWasInterrupted = false;
				// if:  chipsets were already loading,  and another set of chipsets has started loading in the middle of that
				if(	chipsetLoad_prom !== null
				&&	chipsetLoad_prom !== currentChipsetLoad.chipsetLoad_prom ){
					loadWasInterrupted = true;
				} 
				
				// if:  this load-sequence has been replaced by another one
				if(loadWasInterrupted === true){
					// abort this old load-sequence
					vow.doBreak({
						type: "abort",
						message: "Another map has started to load.  Aborting the previous load-sequence."
					});
				}// if:  this load-sequence has been replaced by another one
				else
				{// if:  nothing has interrupted this load-sequence
					// pass along the loaded data
					vow.keep( allNewChipsets );
				}// if:  nothing has interrupted this load-sequence
				
				return vow.promise;
			}// checkForAbortedLoad()
			
			
			
			function afterAllChipsetsLoad( allNewChipsets )
			{
				// after all chipsets have loaded
				// add new loaded chipsets to allLoadedChipsets list
				for(var c=0; c<allNewChipsets.length; c++)
				{// for:  each new chipset
					var thisChipset = allNewChipsets[c];
					var newFile = thisChipset.file;
					var newPic = thisChipset.pic;
					allLoadedChipsets[ newFile ] = newPic;
				}// for:  each new chipset
				// arrange chipsets, old and new, into the order specified by the new newData
				arrangeChipsets();
				// report success
				loadAllChipsets_vow.keep();
			}// afterAllChipsetsLoad()
			
			
			function arrangeChipsets()
			{
				// chipset_array  (overall list for displayed map)
				chipset_array.splice(0);		// clear all previous elements
				for(var c=0; c<newData.chipsets.length; c++)
				{// for:  each chipset specified in the new data
					var filePath = newData.chipsets[c];
					var thisChipset = chipset_array[c] = {};
					thisChipset.file = filePath;
					thisChipset.pic = allLoadedChipsets[filePath];
				}// for:  each chipset specified in the new data
			}// arrangeChipsets()
			
			
			function loadChipsetError( err_obj ){
				loadAllChipsets_vow.doBreak( err_obj );
			}// loadChipsetError()
			
			
			return loadAllChipsets_vow.promise;
		}// loadNewChipsets()
		
		
		
		
		function drawAllTiles()
		{
			// for:  each tile on each layer
			for(var l=0; l<newData.layers.length; l++)
			{// for:  each layer
				for(var xx=0; xx<newData.width; xx++)
				{// for:  map width
					for(var yy=0; yy<newData.height; yy++)
					{// for:  map height
						var location = {
							layer: l,
							x: xx,
							y: yy
						}
						var tileData = newData.layers[l][xx][yy];
						updateTile( location, tileData, newData.tileSize );
					}// for:  map height
				}// for:  map width
			}// for:  each layer
		}// drawAllTiles()
		
		
		
		function finished(){
			drawMap_vow.keep();
		}// finished()
		
		
		
		function drawMapError( err_obj ){
			drawMap_vow.doBreak( err_obj );
		}// drawMapError()
		
		
		return drawMap_vow.promise;
	}// drawMap()
	
	
	
	////////////////////////////////////////////////////////////////////
	// updateTile()
	
	function updateTile( location, tileData, tileSize )
	{
		// EXTERNAL DEPENDENCIES:
		// chipset_array
		// layers_mc_array
		// Rectangle
		// Point
		
		var l = location.layer;
		var xx = location.x;
		var yy = location.y;
		//var tileData = newData.layers[l][xx][yy];
		var tileSize = tileSize || _this.tileSize;
		
		// copy from chipset -> bitmap layer
		var srcChipset_pic = chipset_array[ tileData.chipset ].pic;
		var destLayer_pic = layers_mc_array[l].pic;
		var xCopy = tileData.x * tileSize;
		var yCopy = tileData.y * tileSize;
		var copy = new Rectangle( xCopy,yCopy, tileSize,tileSize );
		var paste = new Point( xx * tileSize,  yy * tileSize );
		destLayer_pic.copyPixels( srcChipset_pic, copy, paste );
	}// updateTile()
	
	
	
	////////////////////////////////////////////////////////////////////
	// isCompressed()
	
	function isCompressed( data ){
		if( data.isCompressed === true )		return true;
		if( data.collision[0] === undefined )		return true;
		return false;
	}// isCompressed()
	
	
	
	////////////////////////////////////////////////////////////////////
	// compressMapData()
	
	function compressMapData( uncompressedData )
	{
		var dataIsCompressed = isCompressed( compressedData );
		if( dataIsCompressed === false )
		{// if:  data is not compressed
			var newData = {};
			
			// copy root variables
			newData.format = uncompressedData.format;
			newData.isCompressed = true;
			newData.tileSize = uncompressedData.tileSize;
			newData.width = uncompressedData.width;
			newData.height = uncompressedData.height;
			
			
			// copy chipsets
			newData.chipsets = [];
			for(var c=0; c<uncompressedData.chipsets.length; c++){
				newData.chipsets[c] = uncompressedData.chipsets[c];
			}// for:  each chipset
			
			
			// compress tiles
			newData.layers = [];
			for(var l=0; l<uncompressedData.layers.length; l++){
				newData.layers[l] = "";
				var thisLayer = uncompressedData.layers[l];
				for(var yy=0; yy<newData.height; yy++){
					for(var xx=0; xx<newData.width; xx++){
						var thisTile = thisLayer[xx][yy];
						var chipset = thisTile.chipset;
						var xChip = thisTile.x;
						var yChip = thisTile.y;
						var compressedValue = compressTileValue( chipset, xChip, yChip );
						newData.layers[l] += compressedValue;
					}// for:  horz tiles
				}// for:  vert tiles
			}// for:  each layer
			// convert layers
			
			
			// compress collision
			newData.collision = "";
			for(var yy=0; yy<newData.height; yy++){
				for(var xx=0; xx<newData.width; xx++){
					var uncompressedValue = Number( uncompressedData.collision[xx][yy] );
					var compressedValue = uncompressedValue.toString( encodingRadix );
					newData.collision += compressedValue;
				}// for:  horz tiles
			}// for:  vert tiles
			
			
			return newData;
		}// if:  data is not compressed
		else
		{// if:  data is already compressed
			// return the data without modifying it
			return uncompressedData;
		}// if:  data is already compressed
		
		
		
		function compressTileValue( chipset, x, y ){
			var chip_code = Number(chipset).toString( encodingRadix );
			var x_code = Number(x).toString( encodingRadix );
			var y_code = Number(y).toString( encodingRadix );
			var output = chip_code + x_code + y_code;
			return output;
		}// compressTileValue()
	}// compressMapData()
	
	
	
	////////////////////////////////////////////////////////////////////
	// decompressMapData()
	
	function decompressMapData( compressedData )
	{
		var dataIsCompressed = isCompressed( compressedData );
		if( dataIsCompressed === true )
		{// if:  data is compressed
			var newData = {};
			
			// copy root variables
			newData.format = compressedData.format;
			newData.isCompressed = false;
			newData.tileSize = compressedData.tileSize;
			newData.width = compressedData.width;
			newData.height = compressedData.height;
			
			
			// copy chipsets
			newData.chipsets = [];
			for(var c=0; c<compressedData.chipsets.length; c++){
				newData.chipsets[c] = compressedData.chipsets[c];
			}// for:  each chipset
			
			
			// de-compress tiles
			newData.layers = [];
			var readIndex = 0;
			var readLength = 3;
			for(var l=0; l<compressedData.layers.length; l++){
				// read data
				readIndex = 0;
				var layer_str = compressedData.layers[l];
				// write data
				newData.layers[l] = [];
				for(var xx=0; xx<newData.width; xx++){
					newData.layers[l][xx] = [];
					for(var yy=0; yy<newData.height; yy++){
						// read data
						var readFrom = ((compressedData.width *yy) +xx) *readLength;
						// write data
						var thisTile = newData.layers[l][xx][yy] = {};
						thisTile.chipset = decompressValue( layer_str.charAt(readFrom) );
						thisTile.x = decompressValue( layer_str.charAt(readFrom+1) );
						thisTile.y = decompressValue( layer_str.charAt(readFrom+2) );
					}// for:  each layer
				}// for:  each layer
			}// for:  each layer
			// convert layers
			
			
			// de-compress collision
			newData.collision = [];
			var readIndex = 0;
			var readLength = 1;
			for(var xx=0; xx<newData.width; xx++){
				newData.collision[xx] = [];
				for(var yy=0; yy<newData.height; yy++){
					var readFrom = (compressedData.width *yy) +xx;
					// decode that character  (base-36 => base-10 decimal)
					// store as a decimal number in that tile-location
					newData.collision[xx][yy] = parseInt( compressedData.collision.charAt(readFrom), 36 );
				}// height
			}// width
			
			
			return newData;
		}// if:  data is compressed
		else
		{// if:  data is already de-compressed
			// return the received data without modifying it
			return compressedData;
		}// if:  data is already de-compressed
		
		
		
		function decompressValue( encodedValue ){
			if( encodedValue=="0x" ){
				return 33;
			}else{
				return parseInt( encodedValue, encodingRadix );
			}
		}// decompressValue()
	}// decompressMapData()
	
	
	
	////////////////////////////////////////////////////////////////////
	// SCROLL
	
	// Get & Set the map's scroll position		(Use this instead of _x and _y from now on)		(This enables a standard scroll interface regardless of scroll method)
	_this.x = 0;
	_this.y = 0;
	
	
	// use scrollRect instead of _x _y
	_this.cacheAsBitmap = true;
	_this.scrollPos = new flash.geom.Point(0,0);		// Used only by this scroll function
	_this.scrollArea = new flash.geom.Rectangle( 0,0, 320,240 );
	var refreshScroll = 2;
	_this.scroll = function( x, y, screenWidth, screenHeight, smoothness, stopAtEdges)
	{
		if( stopAtEdges === undefined )		stopAtEdges = true;
		var tileSize = _this.tileSize || 16;
		if(refreshScroll > 0){
			forceRedraw();
			refreshScroll--;
		}
		// resolve optional parameters
		if(x==undefined && SPRITES.player._x!=undefined)
			var x= SPRITES.player._x;
		if(y==undefined && SPRITES.player._y!=undefined)
			var y= SPRITES.player._y;
		var screenWidth = screenWidth || 320;
		var screenHeight = screenHeight || 240;
		var smoothness = smoothness || 1;
		//
		if(_this.scrollArea.width != screenWidth)		_this.scrollArea.width = screenWidth;
		if(_this.scrollArea.height != screenHeight)		_this.scrollArea.height = screenHeight;
		// calculate view screen center
		var halfWidth = screenWidth / 2;
		var halfHeight = screenHeight / 2;
		// get the map's pixel width
		var mapWidth = _this.width * tileSize;
		var mapHeight = _this.height * tileSize;
		
		var __x = _this.x;
		var __y = _this.y;
		
		// horz
		if( stopAtEdges  &&  x < halfWidth)
		{
			// far-left
			var xTarg = 0;
		}
		else if( stopAtEdges  &&  x > mapWidth-halfWidth)
		{
			// far-right
			var xTarg = -mapWidth+screenWidth;
		}
		else
		{
			// scroll
			var xTarg = -x + halfWidth;
		}
		
		
		// vert
		if( stopAtEdges  &&  y < halfHeight)
		{
			// top
			var yTarg = 0;
		}
		else if( stopAtEdges  &&  y > mapHeight-halfHeight)
		{
			// bottom
			var yTarg = -mapHeight+screenHeight;
		}
		else
		{
			// scroll
			var yTarg = -y + halfHeight;
		}
		
		
		// apply target position,  OR center the map if it's too small
		if( mapWidth < screenWidth )
		{// if:  map is smaller than the screen  (horz)
			// center the map
			var xDiff = screenWidth-mapWidth;
			__x = xDiff/2;
		}// if:  map is smaller than the screen  (horz)
		else
		{
			var xDiff = (xTarg-_this.scrollPos.x);
			if( Math.abs(xDiff) < 0.1 )		xDiff = 0;
			__x += xDiff * smoothness;
		}
		
		
		if( mapHeight < screenHeight )
		{// if:  map is smaller than the screen  (horz)
			// center the map
			var yDiff = screenHeight-mapHeight;
			__y = yDiff/2;
		}// if:  map is smaller than the screen  (horz)
		else
		{
			var yDiff = (yTarg-_this.scrollPos.y);
			if( Math.abs(yDiff) < 0.1 )		yDiff = 0;
			__y += yDiff * smoothness;
		}
		
		
		// round to nearest pixel  (prevent "floating" sprites)
		//_this.x = _this.scrollPos.x = Math.floor(__x);
		//_this.y = _this.scrollPos.y = Math.floor(__y);
		_this.x = _this.scrollPos.x = __x;
		_this.y = _this.scrollPos.y = __y;
		
		// apply a scrollRect
		_this.scrollArea.x = -_this.scrollPos.x;
		_this.scrollArea.y = -_this.scrollPos.y;
		_this.scrollRect = _this.scrollArea;
		
		return _this.scrollArea;
	}// scroll()
	
	
	
	function forceRedraw(){
		_this.layer0_mc.attachBitmap( _this.layer0_mc.pic, 0 );
	}// forceRedraw()
	
	
	
	////////////////////////////////////////////////////////////////////
	// INTERFACE
	
	_this.drawMap = drawMap;
	_this.updateTile = updateTile;
	_this.isCompressed = isCompressed;
	_this.compressMapData = compressMapData;
	_this.decompressMapData = decompressMapData;
	
	return _this;
}// makeMap()
version = null;		delete version;