A Roguelike Game in JavaScript

The code below implements a “Roguelike” game in JavaScript. To see it in action, copy it into an .html file and open that file in a web browser that runs JavaScript.  Or, to play an online version, visit http://thiscouldbebetter.neocities.org/roguelike.html.

If you want to run it locally, you’ll need to download a copy of the NetHack-Tiles-Vanilla.png image to the same directory, and since WordPress seems to be doing some weird automatic formatting on it, you’ll proabably need to get it directly from the URL https://thiscouldbebetter.files.wordpress.com/2013/07/nethack-tiles-vanilla.png Also, if you’re not running the game through a web server you might need to disable some of your browser’s security settings to avoid errors.  Specifically, if you’re using Google’s Chrome browser, you might try using the –disable-web-security command-line option.

It’s not nearly finished yet. You can’t equip things and you can’t throw things. Most of the tools and magic items don’t do anything, and the ones that do don’t do the right thing. Monsters don’t spawn, except for one lonely ant per level. There’s no real goal, and if you go deep enough the dungeon eventually just peters (or actually errors) out. Eventually the plan is to build a full NetHack clone, or at least something very similar, but who knows how realistic that is.

UPDATE 2013-11-02 – I actually “posted” this privately three or four months ago, but I figure I might as well make it public, since it’s clear that I’m not very likely to finish it in the near future anyway.

UPDATE 2014-07-02 – I have updated the code somewhat, notably by changing all the stuff that used to be rendered with various discrete DOM elements to just draw images to a canvas instead. I changed the way the status controls work too, and I haven’t brought all of them back up to code yet.

UPDATE 2016-06-15 – Been a while. I have updated the code so that the entities store just the names of their definitions rather than the whole object, and use that to look up the actual entity definition only when needed. Also, I should note that, if you’re testing this in Google’s Chrome browser against an tileset image stored locally, you’ll need to start the browser with the “–allow-file-access-from-file” command-line flag.

Roguelike

NetHack-Tiles-Vanilla



<html>
<body>

<script type="text/javascript">

// application

function Simulation()
{}
{
	Simulation.prototype.main = function()
	{
		var imageTilesPath = "nethack-tiles-vanilla.png";

		var itemTypeImage = MediaLoaderItemType.Instances.Image;

		var mediaLoader = new MediaLoader
		(
			this, // objectContainingCallback,
			this.main2, // callbackToRunWhenLoadingComplete,
			[
				new MediaLoaderItem("Tiles", itemTypeImage, imageTilesPath),
				new MediaLoaderItem("TilesTransparent", itemTypeImage, imageTilesPath),
			]
		);

		mediaLoader.loadItemsAll();
	}

	Simulation.prototype.main2 = function(mediaLoader)
	{
		var imageTiles = Image.buildFromSystemImage
		(
			"Tiles",
			mediaLoader.items["Tiles"].htmlElement,
			new Coords(640, 432) // sizeInPixels
		);
		imageTiles.onload = null;

		var imageTilesTransparent = Image.buildFromSystemImage
		(
			"TilesTransparent",
			mediaLoader.items["TilesTransparent"].htmlElement,
			new Coords(640, 432) // sizeInPixels
		);
		imageTilesTransparent.onload = null;

		var sizeOfImageTilesInTiles = new Coords(40, 27);

		var imagesForTiles = ImageHelper.sliceImageIntoTiles
		(
			imageTiles,
			sizeOfImageTilesInTiles
		);

		var imagesForTilesTransparent = ImageHelper.sliceImageIntoTiles
		(
			imageTilesTransparent,
			sizeOfImageTilesInTiles
		);

		Camera.initializeStatic();

		var universeDefn = DemoData.buildUniverseDefn(imagesForTiles, imagesForTilesTransparent);

		var venues = universeDefn.buildVenues
		(
			universeDefn,
			universeDefn.venueDefns,
			universeDefn.entityDefnGroups,
			[]
		);

		var universe0 = new Universe
		(
			"Universe0",
			universeDefn,
			venues,
			null // entityForPlayer
		);

		Globals.Instance.initialize
		(
			DemoData.buildFont(),
			100, //realWorldMillisecondsPerTick,
			new Coords(800, 800), //viewSizeInPixels, 
			universe0
		);
	}
}

// extensions

Array.prototype.addLookups = function(keyName)
{
	for (var i = 0; i < this.length; i++)
	{
		var item = this[i];
		this[item[keyName]] = item;
	}
}

Array.prototype.getPropertyWithNameFromEachItem = function(propertyName)
{
	var returnValues = [];

	for (var i = 0; i < this.length; i++)
	{
		var item = this[i];
		returnValues.push(item[propertyName]);
	}

	return returnValues;
}


// classes

function Action(name, ticksToHold, perform, argumentForPerform)
{
	this.name = name;
	this.ticksToHold = ticksToHold;
	this.perform = perform;
	this.argumentForPerform = argumentForPerform;
}

function Activity(defnName, target)
{
	this.defnName = defnName;
	this.target = target;
}
{
	// instance methods

	Activity.prototype.defn = function()
	{
		return Globals.Instance.universe.defn.activityDefns[this.defnName];
	}

	Activity.prototype.initialize = function(actor)
	{
		this.defn().initialize(actor, this);
	}

	Activity.prototype.perform = function(actor)
	{
		this.defn().perform(actor, this);
	}
}

function ActivityDefn(name, initialize, perform)
{
	this.name = name;
	this.initialize = initialize;
	this.perform = perform;
}

function ActorData()
{
	// do nothing?
}
{
	ActorData.prototype.activity_Get = function() { return this._activity; }
	ActorData.prototype.activity_Set = function(actor, value) 
	{ 
		this._activity = value; 
		this._activity.initialize(actor); 
	}
}

function ActorDefn(activityDefnNameInitial)
{
	this.activityDefnNameInitial = activityDefnNameInitial;	
}

function AnimationDefn(name, animationDefnNameNext, frames)
{
	this.name = name;
	this.animationDefnNameNext = animationDefnNameNext;
	this.frames = frames;
}
{
	AnimationDefn.buildManyFromImageSets = function(imageSetsForFrames)
	{
		var returnValues = [];

		var numberOfImageSets = imageSetsForFrames.length;

		for (var i = 0; i < numberOfImageSets; i++)
		{
			var imageSet = imageSetsForFrames[i];

			var animationDefn = new AnimationDefn
			(
				"" + i,
				"" + i,
				AnimationFrame.buildManyFromImages(imageSet)
			);

			returnValues.push(animationDefn);
		}

		return returnValues;
	}
}

function AnimationDefnSet(name, animationDefns)
{
	this.name = name;
	this.animationDefns = animationDefns;

	this.animationDefns.addLookups("name");
}
{
	AnimationDefnSet.buildFromImage = function(image)
	{
		var imageAsImageSets = 
		[
			[
				image
			]
		];

		var returnValue = new AnimationDefnSet
		(
			image.name, 
			AnimationDefn.buildManyFromImageSets
			(
				imageAsImageSets
			)
		);

		return returnValue;
	}

	AnimationDefnSet.buildFromImageForHeadings = function(image, numberOfHeadings)
	{
		var imageAsImageSet = [];
		imageAsImageSet.push(image);
		var imageAsImageSets = [];

		for (var i = 0; i < numberOfHeadings; i++)
		{
			imageAsImageSets.push(imageAsImageSet);
		}

		var returnValue = new AnimationDefnSet
		(
			image.id, 
			AnimationDefn.buildManyFromImageSets
			(
				imageAsImageSets
			)
		);

		return returnValue;
	}

	// instance methods

	AnimationDefnSet.prototype.toRun = function()
	{
		return new AnimationRun(this);
	}

}

function AnimationFrame(visual, ticksToHold)
{
	this.visual = visual;
	this.ticksToHold = ticksToHold;
}
{
	AnimationFrame.buildManyFromImages = function(imagesForFrames)
	{
		var returnValues = [];

		var numberOfImages = imagesForFrames.length;

		for (var i = 0; i < numberOfImages; i++)
		{
			var imageForFrame = imagesForFrames[i];
			var frame = new AnimationFrame
			(
				imageForFrame, 
				1 // ticksToHold
			);

			returnValues.push(frame);
		}

		return returnValues;
	}
}

function AnimationRun(animationDefnSet)
{
	this.animationDefnSet = animationDefnSet;

	var animationDefns= this.animationDefnSet.animationDefns;
	this.animationDefnNameCurrent = animationDefns[0].name;
	this.frameIndexCurrent = 0;
	this.ticksOnFrameCurrent = 0;

	this.visualForFrameCurrent = this.frameCurrent().visual.cloneAsVisual();
}
{
	AnimationRun.prototype.advance = function()
	{
		var frameCurrent = this.frameCurrent();

		this.ticksOnFrameCurrent++;

		if (this.ticksOnFrameCurrent >= frameCurrent.ticksToHold)
		{
			this.ticksOnFrameCurrent = 0;

			this.frameIndexCurrent++;

			var animationDefnCurrent = this.animationDefnCurrent();

			if (this.frameIndexCurrent >= animationDefnCurrent.frames.length)
			{
				this.animationDefnNameCurrent_Set
				(
					animationDefnCurrent.animationDefnNameNext
				);
				this.frameIndexCurrent = 0;
			}
		}
	}

	AnimationRun.prototype.animationDefnCurrent = function()
	{
		return this.animationDefnSet.animationDefns[this.animationDefnNameCurrent];
	}

	AnimationRun.prototype.animationDefnNameCurrent_Set = function(value)
	{
		if (this.animationDefnNameCurrent != value)
		{
			this.animationDefnNameCurrent = value;
			this.frameIndexCurrent = 0;
		}
	}

	AnimationRun.prototype.clone = function()
	{
		return new AnimationRun(this.animationDefnSet);
	}

	AnimationRun.prototype.cloneAsVisual = function()
	{
		return this.clone();
	}

	AnimationRun.prototype.frameCurrent = function()
	{
		return this.animationDefnCurrent().frames[this.frameIndexCurrent];
	}

	AnimationRun.prototype.drawToGraphicsAtPos = function(graphics, drawPos)
	{
		this.visualForFrameCurrent.drawToGraphicsAtPos(graphics, drawPos);
	}

	AnimationRun.prototype.updateForVenue = function(entity)
	{
		this.advance();
	}
}

function ArrayHelper()
{
	// do nothing
}
{
	ArrayHelper.concatenateArrays = function(arraysToConcatenate)
	{
		var returnArray = [];

		for (var i = 0; i < arraysToConcatenate.length; i++)
		{
			var arrayToConcatenate = arraysToConcatenate[i];

			for (var j = 0; j < arrayToConcatenate.length; j++)
			{
				returnArray.push(arrayToConcatenate[j]);
			}
		}

		return returnArray;
	}
}

function Attack(name, dieRollDamage)
{
	this.name = name;
	this.dieRollDamage = dieRollDamage;
}

function Attribute(name, value)
{
	this.name = name;
	this.value = value;
}

function AttributeGroup(name, attributes)
{
	this.name = name;
	this.attributes = attributes;

	this.attributes.addLookups("name");
}

function Bounds(pos, size)
{
	this.pos = pos;
	this.size = size;

	this.max = new Coords(); 
	this.center = new Coords();
	this.recalculateMaxAndCenter();
}
{
	// static methods

	Bounds.doBoundsOverlap = function(bounds0, bounds1)
	{
		var boundsSet = [bounds0, bounds1];

		var doAllDimensionsOverlapSoFar = true;

		for (var d = 0; d < Coords.NumberOfDimensions; d++)
		{		
			var doesDimensionOverlap = Bounds.doBoundsOverlapInDimension
			(
				bounds0, bounds1, d
			);	

			if (doesDimensionOverlap == false)
			{
				doAllDimensionsOverlapSoFar = false;
				break;
			}
		}

		return doAllDimensionsOverlapSoFar;
	}

	Bounds.doBoundsOverlapInDimension = function(bounds0, bounds1, dimensionIndex)
	{
		var returnValue = 
		(
			bounds0.pos.dimension(dimensionIndex) < bounds1.max.dimension(dimensionIndex)
			&& bounds0.max.dimension(dimensionIndex) > bounds1.pos.dimension(dimensionIndex)
		);

		return returnValue;
	}

	Bounds.doBoundsInSetsOverlap = function(boundsSet0, boundsSet1)
	{
		var returnValue = false;

		var numberOfBoundsInSet0 = ( boundsSet0 == null ? 0 : boundsSet0.length );
		var numberOfBoundsInSet1 = ( boundsSet1 == null ? 0 : boundsSet1.length );

		for (var i = 0; i < numberOfBoundsInSet0; i++)
		{
			var boundsFromSet0 = boundsSet0[i];

			for (var j = 0; j < numberOfBoundsInSet1; j++)
			{
				var boundsFromSet1 = boundsSet1[j];

				var doBoundsOverlap = this.doBoundsOverlap
				(
					boundsFromSet0, boundsFromSet1
				);

				if (doBoundsOverlap == true)
				{
					returnValue = true;
					break;
				}				
			}
		}

		return returnValue;
	}

	// instance methods

	Bounds.prototype.recalculateMaxAndCenter = function()
	{
		this.max.overwriteWith(this.pos).add(this.size); 
		this.center.overwriteWith(this.size).divideScalar(2).add(this.pos);
	}
}

function Camera(name, viewSizeInPixels)
{
	this.name = name;
	this.viewSizeInPixels = viewSizeInPixels;

	this.viewSizeInPixelsHalf = this.viewSizeInPixels.clone().divideScalar(2);

	this.entity = new Entity
	(
		this.name + "_CameraEntity",
		Camera.EntityDefn.name,
		new Coords(0, 0)
	);
}
{
	// constants

	Camera.initializeStatic = function()
	{
		Camera.ViewSizeStandard = new Coords(480, 480);

		Camera.EntityDefn = new EntityDefn
		(
			"CameraEntity",
			[ "Camera" ], // categoryNames
			null // properties
		);
	}
}

function Category
(
	name, 
	construct, 
	collide, 
	initializeEntityForVenue, 
	updateEntityForVenue,
	finalizeEntityForVenue
)
{
	this.name = name;
	this.construct = construct;
	this.collide = collide;
	this.initializeEntityForVenue = initializeEntityForVenue;
	this.updateEntityForVenue = updateEntityForVenue;
	this.finalizeEntityForVenue = finalizeEntityForVenue;
}

function CollidableData(defn)
{
	this.defn = defn;
	this.mapCellOccupied = null;
}

function CollidableDefn(blocksMovement, blocksView)
{
	this.blocksMovement = blocksMovement;
	this.blocksView = blocksView;
}
{
	function CollidableDefn_Instances()
	{
		if (CollidableDefn.Instances != null)
		{
			return CollidableDefn.Instances;
		}

		CollidableDefn.Instances = this;

		this.Blocking = new CollidableDefn(true, true);
		this.Concealing = new CollidableDefn(false, true);
		this.Clear = new CollidableDefn(false, false);
	}

	new CollidableDefn_Instances();
}

function Collision(entities)
{
	this.entities = entities;
}

function CollisionHelper()
{}
{
	CollisionHelper.prototype.collideEntities = function(collision, entity0, entity1)
	{
		var entity0DefnCategoryNames = entity0.defn().categoryNames;

		var categories = Globals.Instance.universe.defn.categories;

		for (var c = 0; c < entity0DefnCategoryNames.length; c++)
		{
			var categoryName = entity0DefnCategoryNames[c];
			var category = categories[categoryName];

			// hack
			if (category != null)
			{
				var methodForCollision = category.collide;
				if (methodForCollision != null)
				{
					methodForCollision(entity0, entity1);
				}
			}
		}
	}

	CollisionHelper.prototype.doEntitiesCollide = function(entity0, entity1)
	{
		var returnValue = entity0.loc.posInCells.equals
		(
			entity1.loc.posInCells
		);

		return returnValue;
	}

	CollisionHelper.prototype.findCollisionsBetweenEntitiesInSets = function
	(
		entitySet0, entitySet1
	)
	{
		var returnValues = [];

		var numberOfEntitiesInSet0 = entitySet0 == null ? 0 : entitySet0.length;
		var numberOfEntitiesInSet1 = entitySet1 == null ? 0 : entitySet1.length;

		for (var i = 0; i < numberOfEntitiesInSet0; i++)
		{
			var entityFromSet0 = entitySet0[i];

			for (var j = 0; j < numberOfEntitiesInSet1; j++)
			{
				var entityFromSet1 = entitySet1[j];

				if (this.doEntitiesCollide(entityFromSet0, entityFromSet1) == true)
				{
					var collision = new Collision
					(
						[entityFromSet0, entityFromSet1]
					);
					returnValues.push(collision);
				}				
			}
		}

		return returnValues;
	}
}

function Color(name, symbol, systemColor)
{
	this.name = name;
	this.symbol = symbol;
	this.systemColor = systemColor;
}
{
	// static methods

	Color.getBySymbol = function(symbolToGet)
	{
		var returnValue = Color.Instances._All[symbolToGet];
		return returnValue;
	}

	// instances

	function Color_Instances()
	{
		if (Color.Instances != null) { return Color.Instances; }

		Color.Instances = this;

		this.Transparent = new Color("Transparent", ".", "rgba(0, 0, 0, 0)");

		this.Black 	= new Color("Black", 	"k", "#000000");
		this.Blue 	= new Color("Blue", 	"b", "#0000ff");
		this.BlueDark 	= new Color("BlueDark", "B", "#000080");
		this.Brown	= new Color("Brown", 	"n", "#804000");
		this.Cyan	= new Color("Cyan", 	"c", "#00ffff");
		this.Gray 	= new Color("Gray", 	"a", "#808080");
		this.GrayDark 	= new Color("GrayDark", "A", "#404040");
		this.Green 	= new Color("Green", 	"g", "#00ff00");
		this.GreenDark 	= new Color("GreenDark","G", "#008000");
		this.Orange 	= new Color("Orange", 	"o", "#ff8000");
		this.Purple 	= new Color("Purple", 	"p", "#ff00ff");
		this.Red 	= new Color("Red", 	"r", "#ff0000");
		this.Tan	= new Color("Tan", 	"t", "#aaaa40");
		this.White 	= new Color("White", 	"w", "#ffffff");
		this.Yellow 	= new Color("Yellow", 	"y", "#ffff00");

		this._All = new Array
		(
			this.Transparent,

			this.Black,
			this.Blue,
			this.BlueDark,
			this.Brown,
			this.Cyan,
			this.Gray,
			this.GrayDark,
			this.Green,
			this.GreenDark,
			this.Orange,
			this.Purple,
			this.Red,
			this.Tan,
			this.White,
			this.Yellow	
		);

		for (var i = 0; i < this._All.length; i++)
		{
			var color = this._All[i];
			this._All[color.symbol] = color;
		}
	}	

	new Color_Instances();
}

function Constants()
{}
{
	Constants.Tau = Math.PI * 2;
}

function Constraint(defn, entityConstrained)
{
	this.defn = defn;
	this.entityConstrained = entityConstrained;
}

function ConstraintDefn(name, methodToRun)
{
	this.name = name;
	this.methodToRun = methodToRun;
}
{
	function ConstraintDefn_Instances()
	{
		if (ConstraintDefn.Instances != null) { return; }

		this.Follow = new ConstraintDefn
		(
			"Follow",
			function (constraint)
			{
				var entityConstrained = constraint.entityConstrained;

				// hack
				var entityToFollow = entityConstrained.loc.venue().entitiesByCategoryName
				[
					"Player"
				][0];

				entityConstrained.loc.posInCells.overwriteWith
				(
					entityToFollow.loc.posInCells
				);
			}
		);

		ConstraintDefn.Instances = this;
	}

	new ConstraintDefn_Instances();
}

function ContainerData()
{
	this.items = [];
}
{
	ContainerData.prototype.dropItem = function(actor, itemToDrop)
	{
		var itemsHeld = this.items;

		this.removeItem(actor, itemToDrop);

		itemToDrop.loc.overwriteWith(actor.loc);
		itemToDrop.loc.venue().entitiesToSpawn.push(itemToDrop);
	}

	ContainerData.prototype.removeItem = function(actor, itemToDrop)
	{
		var itemsHeld = this.items;

		var actionSelectNext = Globals.Instance.universe.defn.actions["Item_SelectNext"];
		actionSelectNext.perform(actor);

		var indexOfItemToDrop = itemsHeld.indexOf(itemToDrop);
		itemsHeld.splice(indexOfItemToDrop, 1);

		if (itemsHeld.length == 0)
		{
			this.itemSelected = null;
		}

		this.controlUpdate(actor);
	}

	ContainerData.prototype.pickUpItem = function(actor, itemToPickUp)
	{
		this.items.push(itemToPickUp);
		itemToPickUp.loc.venue().entitiesToRemove.push(itemToPickUp);

		if (this.itemSelected == null)
		{
			this.itemSelected = itemToPickUp;
		}

		this.controlUpdate(actor);
	}

	// control

	ContainerData.prototype.controlUpdate = function(entity, pos)
	{
		if (this.control == null)
		{
			this.control = new ControlContainer
			(
				"containerContainerData",
				pos,
				new Coords(200, 100), // size
				[
					new ControlLabel("labelItems", new Coords(10, 10), "Items"),
					new ControlList
					(
						"listItems", 
						new Coords(10, 20), // pos
						new Coords(180, 70), // size 
						"defn().itemDefn.appearance", // bindingPath 
						this.items
					)
				]
			);
		}

		return this.control;				
	}
}

function Control()
{
	// static class
}
{
	Control.getValueFromObjectAtBindingPath = function(object, bindingPath)
	{
		if (bindingPath != null)
		{
			var bindingAncestry = bindingPath.split(".");
			for (var i = 0; i < bindingAncestry.length; i++)
			{
				var bindingPath = bindingAncestry[i];
				if (bindingPath.endsWith("()") == true)
				{
					bindingPath = bindingPath.substr
					(
						0, bindingPath.indexOf("()")
					);
					var methodToCall = object[bindingPath];
					object = methodToCall.call(object);
				}
				else
				{
					object = object[bindingPath];
				}
			}
		}
	
		return object;
	}

	Control.posAbsoluteOfControl = function(control)
	{
		var returnValue = new Coords(0, 0);

		while (control != null)
		{	
			returnValue.add(control.pos);
			control = control.parent;
		}

		return returnValue;
	}
}

function ControlContainer(name, pos, size, children)
{
	this.name = name;
	this.pos = pos;
	this.size = size;
	this.children = children;

	for (var i = 0; i < this.children.length; i++)
	{
		var child = this.children[i];

		child.parent = this;
		this.children[child.name] = child;
	}
}
{
	ControlContainer.prototype.drawToGraphics = function(graphics)
	{
		var drawPos = Control.posAbsoluteOfControl(this);

		graphics.fillStyle = "rgba(0, 0, 0, 0)";
		graphics.strokeStyle = "Gray";
		graphics.fillRect(drawPos.x, drawPos.y, this.size.x, this.size.y);
		graphics.strokeRect(drawPos.x, drawPos.y, this.size.x, this.size.y);

		for (var i = 0; i < this.children.length; i++)
		{
			var child = this.children[i];
			child.drawToGraphics(graphics);
		}
	}
}

function ControlLabel(name, pos, textFormat, dataContexts, bindingPaths)
{
	this.name = name;
	this.pos = pos;
	this.textFormat = textFormat;
	this.dataContexts = dataContexts;
	this.bindingPaths = bindingPaths;
}
{
	ControlLabel.prototype.drawToGraphics = function(graphics)
	{
		var drawPos = Control.posAbsoluteOfControl(this);
		
		var text = this.textFormat;

		if (this.dataContexts != null)
		{
			var textParts = text.split("^");

			text = "";

			for (var i = 0; i < this.dataContexts.length; i++)
			{
				var dataContext = this.dataContexts[i];
				var bindingPath = this.bindingPaths[i];

				var boundValue = Control.getValueFromObjectAtBindingPath
				(
					dataContext,
					bindingPath
				);		
				
				text += textParts[i];
				text += boundValue;
			}

			if (textParts.length > this.dataContexts.length)
			{
				text += textParts[this.dataContexts.length];
			}
		}

		graphics.fillStyle = "Gray";
		graphics.fillText
		(
			text,
			drawPos.x, drawPos.y
		);
	}
}

function ControlList(name, pos, size, bindingPath, listables)
{
	this.name = name;
	this.pos = pos;
	this.size = size;
	this.bindingPath = bindingPath;
	this.listables = listables;
}
{
	ControlList.prototype.drawToGraphics = function(graphics)
	{
		var drawPos = Control.posAbsoluteOfControl(this);

		graphics.fillStyle = "rgba(0, 0, 0, 0)";
		graphics.strokeStyle = "Gray";
		graphics.fillRect(drawPos.x, drawPos.y, this.size.x, this.size.y);
		graphics.strokeRect(drawPos.x, drawPos.y, this.size.x, this.size.y);

		graphics.fillStyle = "Gray";
		for (var i = 0; i < this.listables.length; i++)
		{
			var listable = this.listables[i];
			drawPos.y += 10; // hack
			var itemText = Control.getValueFromObjectAtBindingPath
			(
				listable, 
				this.bindingPath
			);
			graphics.fillText
			(
				itemText,
				drawPos.x,
				drawPos.y	
			)
		}
	}
}

function Coords(x, y)
{
	this.x = x;
	this.y = y;
}
	// instances

	function Coords_Instances()
	{
		if (Coords.Instances != null) { return Coords.Instances; }

		Coords.Instances = this;

		this.Ones 	= new Coords(1, 1);
		this.Twos 	= new Coords(2, 2);
		this.Zeroes 	= new Coords(0, 0);

		this.DirectionE 	= new Coords(1, 0);
		this.DirectionN 	= new Coords(0, -1);
		this.DirectionNE 	= new Coords(1, -1);
		this.DirectionNW 	= new Coords(-1, -1);
		this.DirectionS 	= new Coords(0, 1);
		this.DirectionSE 	= new Coords(1, 1);
		this.DirectionSW 	= new Coords(-1, 1);
		this.DirectionW 	= new Coords(-1, 0);

		this._DirectionsByHeading = 
		[
			this.DirectionE,
			this.DirectionSE,
			this.DirectionS,
			this.DirectionSW,
			this.DirectionW,
			this.DirectionNW,
			this.DirectionN,
			this.DirectionNE,
		];

		this.Temp = new Coords(0, 0);
	}

	new Coords_Instances();

{
	// constants

	Coords.NumberOfDimensions = 2;

	// instance methods

	Coords.prototype.absolute = function()
	{
		this.x = Math.abs(this.x);
		this.y = Math.abs(this.y);

		return this;
	}

	Coords.prototype.add = function(other)
	{
		this.x += other.x;
		this.y += other.y;

		return this;
	}

	Coords.prototype.clear = function()
	{
		this.x = 0;
		this.y = 0;

		return this;
	}

	Coords.prototype.clone = function()
	{
		var returnValue = new Coords(this.x, this.y);

		return returnValue;
	}

	Coords.prototype.dimension = function(dimensionIndex)
	{
		var returnValue;

		if (dimensionIndex == 0)
		{
			returnValue = this.x;
		}
		else
		{
			returnValue = this.y;
		}

		return returnValue;
	}

	Coords.prototype.dimension_Set = function(dimensionIndex, valueToSet)
	{
		if (dimensionIndex == 0)
		{
			this.x = valueToSet;
		}
		else
		{
			this.y = valueToSet;
		}

		return this;
	}

	Coords.prototype.dimensionIndexOfLargest = function(indexOfDimensionThatLosesTies)
	{
		return this.dimensionIndexOfSmallestOrLargest
		(
			indexOfDimensionThatLosesTies, -1
		);
	}

	Coords.prototype.dimensionIndexOfSmallest = function(indexOfDimensionThatLosesTies)
	{
		return this.dimensionIndexOfSmallestOrLargest
		(
			indexOfDimensionThatLosesTies, 1
		);
	}

	Coords.prototype.dimensionIndexOfSmallestOrLargest = function
	(
		indexOfDimensionThatLosesTies, 
		multiplier
	)
	{
		var returnValue;

		var value = ( Math.abs(this.x) - Math.abs(this.y) ) * multiplier;

		if (value > 0)
		{
			returnValue = 1;
		}
		else if (value < 0)
		{
			returnValue = 0;
		}
		else if (indexOfDimensionThatLosesTies != null)
		{
			returnValue = indexOfDimensionThatLosesTies;
		}

		return returnValue;
	}

	Coords.prototype.directions = function()
	{
		if (this.x > 0)
		{
			this.x = 1;
		}
		else if (this.x < 0)
		{
			this.x = -1;
		}

		if (this.y > 0)
		{
			this.y = 1;
		}
		else if (this.y < 0)
		{
			this.y = -1;
		}

		return this;
	}

	Coords.prototype.divide = function(other)
	{
		this.x /= other.x;
		this.y /= other.y;

		return this;
	}

	Coords.prototype.divideScalar = function(scalar)
	{
		this.x /= scalar;
		this.y /= scalar;

		return this;
	}

	Coords.prototype.equals = function(other)
	{
		return (this.x == other.x && this.y == other.y);
	}

	Coords.prototype.floor = function()
	{
		this.x = Math.floor(this.x);
		this.y = Math.floor(this.y);

		return this;
	}

	Coords.prototype.invert = function(scalar)
	{
		this.x = 0 - this.x;
		this.y = 0 - this.y;

		return this;
	}

	Coords.prototype.isWithinRange = function(range)
	{
		var returnValue = 
		(
			this.x >= 0 
			&& this.x <= range.x
			&& this.y >= 0
			&& this.y <= range.y
		);

		return returnValue;
	}

	Coords.prototype.magnitude = function()
	{
		var returnValue = Math.sqrt(this.x * this.x + this.y * this.y);

		return returnValue;
	}

	Coords.prototype.multiply = function(other)
	{
		this.x *= other.x;
		this.y *= other.y;

		return this;
	}

	Coords.prototype.multiplyScalar = function(scalar)
	{
		this.x *= scalar;
		this.y *= scalar;

		return this;
	}

	Coords.prototype.normalize = function()
	{
		var returnValue;

		var magnitude = this.magnitude();
		if (magnitude == 0)
		{
			returnValue = this;
		}
		{
			returnValue = this.divideScalar(magnitude);
		}

		return returnValue;
	}

	Coords.prototype.overwriteWith = function(other)
	{
		this.x = other.x;
		this.y = other.y;

		return this;
	}

	Coords.prototype.overwriteWithDimensions = function(x, y)
	{
		this.x = x;
		this.y = y;

		return this;
	}

	Coords.prototype.random = function()
	{
		var randomizer = Globals.Instance.randomizer;

		this.x = Math.floor(this.x * randomizer.getNextRandom());
		this.y = Math.floor(this.y * randomizer.getNextRandom());

		return this;
	}

	Coords.prototype.round = function()
	{
		this.x = Math.round(this.x);
		this.y = Math.round(this.y);

		return this;
	}

	Coords.prototype.subtract = function(other)
	{
		this.x -= other.x;
		this.y -= other.y;

		return this;
	}

	Coords.prototype.sumOfXAndY = function()
	{
		return this.x + this.y;
	}

	Coords.prototype.toString = function()
	{
		return "(" + this.x + "," + this.y + ")";
	}

	Coords.prototype.trimToRange = function(rangeToTrimTo)
	{
		if (this.x < 0)
		{
			this.x = 0;
		}
		else if (this.x > rangeToTrimTo.x)
		{
			this.x = rangeToTrimTo.x;
		}

		if (this.y < 0)
		{
			this.y = 0;
		}
		else if (this.y > rangeToTrimTo.y)
		{
			this.y = rangeToTrimTo.y;
		}

		return this;
	}
}

function DeviceData(numberOfCharges)
{
	this.numberOfCharges = numberOfCharges;
}
{
	DeviceData.prototype.use = function(userEntity, deviceEntity, targetEntity)
	{
		if (this.numberOfCharges > 0)
		{
			this.numberOfCharges--;

			deviceEntity.defn().deviceDefn.use
			(
				userEntity, deviceEntity, targetEntity
			);
		}

		if (this.numberOfCharges <= 0)
		{
			var deviceDefn = deviceEntity.defn().deviceDefn;	
			if (deviceDefn.consumedWhenAllChargesUsed == true)
			{
				userEntity.containerData.removeItem
				(
					userEntity, deviceEntity
				);
			}
			else
			{
				Font.spawnMessageFloater
				(
					deviceEntity.defn().name, 
					Message.getTextForName("NothingHappens"), 
					userEntity.loc
				);
			} 
		}
	}
}

function DeviceDefn(chargesMax, consumedWhenAllChargesUsed, effectsToApply)
{
	this.chargesMax = chargesMax;
	this.consumedWhenAllChargesUsed = consumedWhenAllChargesUsed;
	this.effectsToApply = effectsToApply;
}
{
	DeviceDefn.prototype.use = function(userEntity, deviceEntity, targetEntity)
	{
		for (var i = 0; i < this.effectsToApply.length; i++)
		{
			var effect = this.effectsToApply[i];

			effect.applyToEntity(deviceEntity, targetEntity);
		}
	}
}

function DiceRoll(expression)
{
	this.expression = expression;
}
{
	// static methods

	DiceRoll.roll = function(expression)
	{
		var diceRoll = DiceRoll.Instance;
		diceRoll.overwriteWithExpression(expression)
		var returnValue = diceRoll.roll();
		return returnValue;
	}

	// instances

	DiceRoll.Instance = new DiceRoll("1");

	// instance methods

	DiceRoll.prototype.overwriteWithExpression = function(expression)
	{
		this.expression = expression;

		return this;
	}

	DiceRoll.prototype.roll = function()
	{
		var expression = this.expression;

		var totalSoFar = 0;

		var terms = 
		(
			expression.indexOf("+") < 0 
			? [expression] 
			: expression.split("+")
		);

		for (var t = 0; t < terms.length; t++)
		{
			var term = terms[t];

			if (term.indexOf("d") < 0)
			{
				var valueConstant = parseInt(term);
				totalSoFar += valueConstant;
			}
			else
			{
				var tokens = term.split("d");	
				var numberOfDice = parseInt(tokens[0]);
				var sidesPerDie = parseInt(tokens[1]);

				for (var i = 0; i < numberOfDice; i++)
				{
					var valueRolledOnDie = 
						1 
						+ Math.floor
						(
							Globals.Instance.randomizer.getNextRandom() 
							* sidesPerDie
						);

					totalSoFar += valueRolledOnDie;
				}
			}
		}

		return totalSoFar;
	}
}

function DisplayHelper()
{}
{
	DisplayHelper.prototype.clear = function()
	{
		this.graphics.fillStyle = "Black";
		this.graphics.fillRect
		(
			0, 0,
			this.viewSizeInPixels.x,
			this.viewSizeInPixels.y
		);
	}

	DisplayHelper.prototype.drawControl = function(controlToDraw)
	{
		controlToDraw.drawToGraphics(this.graphics);	
	}

	DisplayHelper.prototype.drawMap = function(map)
	{
		var cellPos = new Coords(0, 0);
		var drawPos = new Coords(0, 0);
		for (var y = 0; y < map.sizeInCells.y; y++)
		{
			cellPos.y = y;

			for (var x = 0; x < map.sizeInCells.x; x++)
			{
				cellPos.x = x;

				var cell = map.cellAtPos(cellPos);
				var cellTerrain = cell.terrain;
				var terrainImage = cellTerrain.image;

				drawPos.overwriteWith
				(
					cellPos
				).multiply
				(
					map.cellSizeInPixels
				);

				this.graphics.drawImage
				(
					terrainImage.systemImage,
					drawPos.x,
					drawPos.y					
				);	

				var entitiesInCell = cell.entitiesPresent;
				for (var i = 0; i < entitiesInCell.length; i++)
				{
					var entity = entitiesInCell[i];
					var visual = entity.drawableData.visual;
					visual.drawToGraphicsAtPos
					(
						this.graphics,
						drawPos
					);
				}
			}
		}
	}

	DisplayHelper.prototype.drawVenue = function(venue)
	{
		var map = venue.map;
		this.drawMap(map);
	}

	DisplayHelper.prototype.initialize = function(viewSizeInPixels)
	{
		this.viewSizeInPixels = viewSizeInPixels;

		this.canvas = document.createElement("canvas");
		this.canvas.width = this.viewSizeInPixels.x;
		this.canvas.height = this.viewSizeInPixels.y;
		this.graphics = this.canvas.getContext("2d");

		document.body.appendChild(this.canvas);
	}
}

function DrawableData(isVisible)
{
	this.isVisible = isVisible;
}

function DrawableDefn(visual, sizeInPixels, zIndex)
{
	this.visual = visual;
	this.sizeInPixels = sizeInPixels;
	this.zIndex = zIndex;

	if (this.sizeInPixels == null)
	{
		this.sizeInPixels = Coords.Instances.Zeroes;
	}

	if (this.zIndex == null)
	{
		this.zIndex = 0;
	}

	this.sizeInPixelsHalf = this.sizeInPixels.clone().divideScalar(2);
}

function DynamicData(defn)
{
	this.vel = defn.vel;
	this.accel = defn.accel;
}

function DynamicDefn(vel, accel)
{
	this.vel = vel;
	this.accel = accel;
}

function Effect(defn)
{
	this.defn = defn;
}
{
	Effect.prototype.applyToEntity = function(actingEntity, targetEntity)
	{
		this.defn.apply(actingEntity, targetEntity);
	}

	Effect.prototype.clone = function()
	{
		return new Effect(this.defn);
	}
}

function EffectDefn(name, apply)
{
	this.name = name;
	this.apply = apply;
}

function EffectableData()
{
	this.effects = [];
}

function Entity(name, defnName, pos, propertyValues)
{
	this.name = name;
	this.defnName = defnName;
	this.loc = new Location
	(
		null, // venueName
		pos
	);

	if (propertyValues != null)
	{
		for (var i = 0; i < propertyValues.length; i++)
		{
			var propertyValue = propertyValues[i];
			var propertyName = propertyValue.constructor.name;
			propertyName = 
				propertyName.substring(0, 1).toLowerCase() 
				+ propertyName.substring(1);

			this[propertyName] = propertyValue;
		}
	}
}
{
	Entity.EntityText = "Entity";

	Entity.fromDefn = function(name, defn, pos, propertyValues)
	{
		var returnValue = new Entity(name, null, pos, propertyValues);
		returnValue._defn = defn;
		return returnValue;
	}

	Entity.prototype.defn = function()
	{
		var returnValue;

		if (this._defn == null)
		{
			returnValue = Globals.Instance.universe.defn.entityDefns[this.defnName];
		}
		else
		{
			returnValue = this._defn;
		}

		return returnValue;
	}
}

function EntityDefn
(
	name, 
	categoryNames, 
	propertyValues
)
{
	this.name = name;
	this.categoryNames = categoryNames;

	if (propertyValues != null)
	{
		for (var i = 0; i < propertyValues.length; i++)
		{
			var propertyValue = propertyValues[i];
			var propertyName = propertyValue.constructor.name;
			propertyName = 
				propertyName.substring(0, 1).toLowerCase() 
				+ propertyName.substring(1);

			this[propertyName] = propertyValue;
		}
	}

	EntityDefnGroup.prototype.toXmlElement = function()
	{
		return new XmlElement
		(
			this.constructor.name,
			// attributeNameValuePairs
			[	
				[ "name", this.name ],
			],
			// children
			[
				// todo
			]
		);
	}
}

function EntityDefnGroup(name, relativeFrequency, entityDefns)
{
	this.name = name;
	this.relativeFrequency = relativeFrequency;
	this.entityDefns = entityDefns;
}
{
	EntityDefnGroup.prototype.toXmlElement = function()
	{
		return new XmlElement
		(
			this.constructor.name,
			// attributeNameValuePairs
			[	
				[ "name", this.name ],
				[ "relativeFrequency", this.relativeFrequency ],
			],
			// children
			[
				new XmlElement("EntityDefns", [], XmlElement.buildManyFromXmlizables(this.entityDefns)),
			]
		);
	}
}

function EphemeralData(defn)
{
	this.ticksToLive = defn.ticksToLive;
}

function EphemeralDefn(ticksToLive)
{
	this.ticksToLive = ticksToLive;
}

function EquipmentSocket(defn, itemEquipped)
{
	this.defn = defn;
	this.itemEquipped = itemEquipped;
}
{
	// control

	EquipmentSocket.prototype.controlUpdate = function(entity)
	{
		if (this.control == null)
		{
			this.control = new ControlContainer
			(
				"containerEquipmentSocket",
				new Coords(0, 0), // pos
				new Coords(100, 100), // size
				[
					new ControlLabel("labelName", new Coords(10, 10), this.defn.name),
				]
			);
		}

		return this.control;				
	}
}

function EquipmentSocketSet(equipmentSocketDefnSet)
{
	this.equipmentSocketDefnSet = equipmentSocketDefnSet;
	this.sockets = [];

	var socketDefns = equipmentSocketDefnSet.socketDefns;

	for (var i = 0; i < socketDefns.length; i++)
	{
		var socketDefn = socketDefns[i];

		var socket = new EquipmentSocket(socketDefn, null);

		this.sockets.push(socket);
	}	
}

function EquipmentSocketDefn(name, namesOfCategoriesAllowed)
{
	this.name = name;
	this.namesOfCategoriesAllowed = namesOfCategoriesAllowed;
}

function EquipmentSocketDefnSet(name, socketDefns)
{
	this.name = name;
	this.socketDefns = socketDefns;
}

function EquippableData(equippableDefn)
{
	this.equipmentSocketSet = new EquipmentSocketSet
	(
		equippableDefn.equipmentSocketDefnSet
	);	
}
{
	// control

	EquippableData.prototype.controlUpdate = function(entity)
	{
		if (this.control == null)
		{
			this.control = new ControlContainer
			(
				"containerEquippableData",
				new Coords(0, 0), // pos
				new Coords(100, 100), // size
				[
					new ControlLabel("labelEquipment", new Coords(10, 10), "Equipment:"),
				]
			);
		}

		return this.control;				
	}
}

function EquippableDefn(equipmentSocketDefnSet)
{
	this.equipmentSocketDefnSet = equipmentSocketDefnSet;
}

function FieldOfView()
{
	// do nothing
}
{
	FieldOfView.prototype.setVenueAndRangeAndViewerPos = function
	(
		venue, distanceFromEyeMax, eyePos
	)
	{
		this.venue = venue;
		this.distanceFromEyeMax = distanceFromEyeMax;
		this.eyePos = eyePos;
	}

	FieldOfView.prototype.calculateCellPositionsVisible = function()
	{
		var numberOfCellPositionsMax = 
			4 
			* this.distanceFromEyeMax 
			* this.distanceFromEyeMax;

		if 
		(
			this.cellPositionsVisible == null 
			|| this.cellPositionsVisible.length < numberOfCellPositionsMax
		)
		{
			this.cellPositionsVisible = [];

			for (var i = 0; i < numberOfCellPositionsMax; i++)
			{
				this.cellPositionsVisible.push(new Coords(0, 0));
			}
		}

		var tau = Constants.Tau;

		var eyePosCentered = this.eyePos.clone().add(new Coords(.5, .5));

		var angleRangeSetNotYetBlocked = new RangeSet
		([
			new Range(0, 1)
		]);

		var displacementFromEyeToCell = new Coords(0, 0);	

		var directionsForSides = 
		[
			new Coords(-1, 1),
			new Coords(-1, -1),
			new Coords(1, -1),
			new Coords(1, 1),
		];

		var cornerAddendsForSides = 
		[
			new Coords(0, 0),
			new Coords(0, -1),
			new Coords(1, 0),
			new Coords(0, 1),
		];

		var cellPos = new Coords(0, 0);
		var cellPosRelative = new Coords(0, 0);
		var vertexPositionsRelative = [ new Coords(0, 0), new Coords(0, 0) ];

		this.cellPositionsVisible[0] = this.eyePos.clone();
		this.numberOfCellsVisible = 1;

		var map = this.venue.map;

		for (var r = 1; r <= this.distanceFromEyeMax; r++)
		{
			cellPosRelative.overwriteWithDimensions(r, 0);

			vertexPositionsRelative[0].overwriteWith(new Coords(r - .5, -.5));			
			vertexPositionsRelative[1].overwriteWith(new Coords(r - .5, .5));

			for (var s = 0; s < directionsForSides.length; s++)
			{
				var direction = directionsForSides[s];

				vertexPositionsRelative[1].add(cornerAddendsForSides[s]);

				for (var d = 0; d < r; d++)
				{
					cellPos.overwriteWith(this.eyePos).add(cellPosRelative);

					if (cellPos.isWithinRange(map.sizeInCellsMinusOnes) == true)
					{
						var cellSpan = new Range
						(
							NumberHelper.atan3(vertexPositionsRelative[0]),
							NumberHelper.atan3(vertexPositionsRelative[1])
						);

						var cellSpanAsSet = new RangeSet( [ cellSpan ] );

						cellSpanAsSet.splitRangesThatSpanPeriod(1);

						if (cellSpanAsSet.overlaps(angleRangeSetNotYetBlocked) == true)
						{
							var cellPosVisible = this.cellPositionsVisible
							[
								this.numberOfCellsVisible
							];

							cellPosVisible.overwriteWith
							(
								cellPos
							);

							this.numberOfCellsVisible++;

							var cellAtPos = map.cellAtPos(cellPos);
							if (cellAtPos.terrain.blocksVision == true)
							{
								angleRangeSetNotYetBlocked.subtract
								(
									cellSpanAsSet
								);
							}
							else 
							{
								var entitiesPresent = this.venue.map.cellAtPos
								(
									cellPos
								).entitiesPresent;

								for (var b = 0; b < entitiesPresent.length; b++)
								{
									var entityPresent = entitiesPresent[b];
									if (entityPresent.defn().collidableDefn.blocksView == true)
									{							
										angleRangeSetNotYetBlocked.subtract(cellSpanAsSet);
									}
								}
							}
						}
					}

					cellPosRelative.add(direction);
					vertexPositionsRelative[0].overwriteWith(vertexPositionsRelative[1]);
					vertexPositionsRelative[1].add(direction);
				}
			}
		}
	}
}

function Font(charactersAvailable, characterSize, characterImages)
{
	this.charactersAvailable = charactersAvailable;
	this.characterSize = characterSize;
	this.characterImages = characterImages;
}
{
	Font.prototype.buildEntityDefnForText = function
	(
		visualForIcon, text, isFloater
	)
	{
		text = text.toUpperCase();

		var visualsForCharacters = [];

		visualsForCharacters.push(visualForIcon);

		for (var i = 0; i < text.length; i++)
		{
			var character = text[i];

			var characterIndex = this.charactersAvailable.indexOf
			(
				character
			);

			if (characterIndex >= 0)
			{
				var characterImage = this.characterImages
				[
					characterIndex
				];

				var visualForCharacter = new VisualOffset
				(
					characterImage,
					new Coords(i * this.characterSize.x, 0)
				);

				visualsForCharacters.push
				(
					visualForCharacter
				);
			}
		}	

		var entityDefnCategoryNames = 
		[
			"Drawable",
		];

		var entityDefnProperties = 
		[
			new DrawableDefn
			(
				new VisualSet(text, visualsForCharacters),
				null, // sizeInPixels
				2 // zIndex
			),
		]

		var ticksToLive = (isFloater == true ? 16 : null);

		if (ticksToLive != null)
		{
			entityDefnCategoryNames.push("Ephemeral");
			entityDefnProperties.push(new EphemeralDefn(ticksToLive));
		}

		var velocity = (isFloater == true ? new Coords(0, -.4) : null);

		if (velocity != null)
		{
			entityDefnCategoryNames.push("Dynamic");
			entityDefnProperties.push
			(
				new DynamicDefn(velocity, Coords.Instances.Zeroes)
			);
		}

		var entityDefn = new EntityDefn
		(
			"Message_" + text,
			entityDefnCategoryNames,
			entityDefnProperties
		);

		return entityDefn;
	}

	Font.spawnMessage = function
	(
		messageIconName, 
		messageText, 
		loc,
		isFloater
	)
	{
		var universe = Globals.Instance.universe;
		var entityDefns = universe.defn.entityDefns;

		var messageIcon = 
		(
			messageIconName == null 
			? null 
			: entityDefns[messageIconName].drawableDefn.visual
		);

		var entityMessage = Entity.fromDefn
		(
			messageText,
			Globals.Instance.font.buildEntityDefnForText
			(
				messageIcon,
				messageText.toUpperCase(),
				isFloater
			),
			loc.posInCells.clone()
		);

		//entityMessage.drawableData.isVisible = true;

		universe.venueCurrent.entitiesToSpawn.push(entityMessage);
	}

	Font.spawnMessageFixed = function
	(
		messageText, 
		loc
	)
	{
		Font.spawnMessage(null, messageText, loc, false);
	}

	Font.spawnMessageFloater = function
	(
		messageIconName, 
		messageText, 
		loc
	)
	{
		Font.spawnMessage(messageIconName, messageText, loc, true);
	}
}

function Globals()
{
	this.htmlElementLibrary = new HTMLElementLibrary();
	this.randomizer = new RandomizerLCG
	(
	        1103515245, // multiplier
	        12345, // addend
	        Math.pow(2.0, 31), // modulus
	        0.12345 // firstRandom
	);
	this.sightHelper = new SightHelper();
}
{
	Globals.prototype.initialize = function
	(
		font,
		realWorldMillisecondsPerTick, 
		viewSizeInPixels, 
		universe
	)	
	{
		this.collisionHelper = new CollisionHelper();
		this.font = font;

		this.inputHelper = new InputHelper();
		this.inputHelper.initialize();

		this.realWorldMillisecondsPerTick = realWorldMillisecondsPerTick;

		this.displayHelper = new DisplayHelper();
		this.displayHelper.initialize(viewSizeInPixels);

		this.universe = universe;

		this.timer = setInterval
		(
			Globals.Instance.processTick.bind(this), 
			this.realWorldMillisecondsPerTick
		);
	}

	Globals.prototype.processTick = function()
	{
		this.inputHelper.updateForTick();
		this.universe.update();
	}
}
{
	Globals.Instance = new Globals();
}

function Heading()
{}
{
	// constants

	Heading.numberOfHeadings = 8;

	// static methods

	Heading.fromCoords = function(coordsToConvert)
	{
		var returnValue = Math.floor
		(
			Math.atan2
			(
				coordsToConvert.y, 
				coordsToConvert.x
			)
			* Heading.numberOfHeadings
			/ (2 * Math.PI)
		);

		if (returnValue < 0)
		{
			returnValue += Heading.numberOfHeadings;
		}

		return returnValue;
	}
}

function HTMLElementLibrary()
{
	this.idNext = 0;
	this.elements = [];
}
{
	HTMLElementLibrary.Instance = new HTMLElementLibrary();

	HTMLElementLibrary.prototype.createElement = function(tagName)
	{
		var returnValue = document.createElement(tagName);
		returnValue.id = "_" + this.idNext;
		this.idNext++;

		this.elements[returnValue.id] = returnValue;

		return returnValue;
	}

	HTMLElementLibrary.prototype.getElementByID = function(idToGet)
	{
		return this.elements[idToGet];
	}

}

function HTMLHelper()
{}
{
	HTMLHelper.Newline = "<br />";

	HTMLHelper.HandleHideOrShowControlEvent = function(event)
	{
		HTMLHelper.HideOrShowControl(event.target);
	}

	HTMLHelper.HideOrShowControl = function(controlToHideOrShow)
	{	
		var htmlElementToHideOrShow = controlToHideOrShow.elementToHideOrShow;

		if (controlToHideOrShow.isExpanded == false)
		{
			controlToHideOrShow.isExpanded = true;
			controlToHideOrShow.innerHTML = "-";
			htmlElementToHideOrShow.style.display = "";
		}
		else
		{
			controlToHideOrShow.isExpanded = false;
			htmlElementToHideOrShow.style.display = "none";
			controlToHideOrShow.innerHTML = "+";
		}
	}
}

function Image()
{}
{
	// static methods

	Image.buildFromFilePath = function(name, filePath)
	{
		var returnValue = new Image();

		returnValue.name = name;
		returnValue.filePath = filePath;
		returnValue.systemImage = document.createElement("img");
		returnValue.systemImage.src = filePath;

		return returnValue;
	}

	Image.buildFromSystemImage = function(name, systemImage, sizeInPixels)
	{
		var returnValue = new Image();

		returnValue.name = name;
		returnValue.htmlElementID = systemImage.id;
		returnValue.filePath = systemImage.src;
		returnValue.sizeInPixels = sizeInPixels;
		returnValue.systemImage = systemImage;

		return returnValue;
	}

	// instance methods

	Image.prototype.clone = function()
	{
		var returnValue = new Image();

		returnValue.name = name;
		returnValue.filePath = this.filePath;
		returnValue.sizeInPixels = this.sizeInPixels.clone();
		returnValue.systemImage = this.systemImage;

		return returnValue;
	}

	Image.prototype.cloneAsVisual = function()
	{
		return this.clone();
	}

	Image.prototype.drawToGraphicsAtPos = function(graphics, drawPos)
	{
		graphics.drawImage
		(
			this.systemImage,
			drawPos.x,
			drawPos.y
		);
	}

	Image.prototype.updateForVenue = function()
	{
		// do nothing
	}
}

function ImageHelper()
{}
{
	// static methods

	ImageHelper.buildImageFromStrings = function(name, stringsForPixels)
	{
		return ImageHelper.buildImageFromStringsScaled
		(
			name, Coords.Instances.Ones, stringsForPixels
		);
	}

	ImageHelper.buildImagesFromStringArrays = function(name, stringArraysForImagePixels)
	{
		var returnValue = [];

		for (var i = 0; i < stringArraysForImagePixels.length; i++)
		{
			var stringsForImagePixels = stringArraysForImagePixels[i];
			var image = ImageHelper.buildImageFromStrings(name + i, stringsForImagePixels);
			returnValue.push(image);
		}

		return returnValue;
	}

	ImageHelper.buildImageFromStringsScaled = function(name, scaleFactor, stringsForPixels)
	{
		var sizeInPixels = new Coords
		(
			stringsForPixels[0].length, 
			stringsForPixels.length
		);

		var htmlElementLibrary = Globals.Instance.htmlElementLibrary;

		var canvas = document.createElement("canvas");
		canvas.width = sizeInPixels.x * scaleFactor.x;
		canvas.height = sizeInPixels.y * scaleFactor.y;

		var graphics = canvas.getContext("2d");

		var pixelPos = new Coords(0, 0);
		var colorForPixel;

		for (var y = 0; y < sizeInPixels.y; y++)
		{
			var stringForPixelRow = stringsForPixels[y];
			pixelPos.y = y * scaleFactor.y;

			for (var x = 0; x < sizeInPixels.x; x++)
			{
				var charForPixel = stringForPixelRow[x];
				pixelPos.x = x * scaleFactor.x;

				colorForPixel = Color.getBySymbol(charForPixel);

				graphics.fillStyle = colorForPixel.systemColor;
				graphics.fillRect
				(
					pixelPos.x, 
					pixelPos.y, 
					scaleFactor.x, 
					scaleFactor.y
				);				
			}
		}

		var imageFromCanvasURL = canvas.toDataURL("image/png");
		var htmlImageFromCanvas = htmlElementLibrary.createElement("img");
		htmlImageFromCanvas.width = canvas.width;
		htmlImageFromCanvas.height = canvas.height;
		htmlImageFromCanvas.src = imageFromCanvasURL;

		var returnValue = Image.buildFromSystemImage
		(
			name, 
			htmlImageFromCanvas, 
			sizeInPixels
		);

		return returnValue;
	}

	ImageHelper.copyRegionFromImage = function(imageToCopyFrom, regionPos, regionSize)
	{
		var canvas		 = document.createElement("canvas");
		canvas.id		 = "region_" + regionPos.x + "_" + regionPos.y;
		canvas.width		 = regionSize.x;
		canvas.height		 = regionSize.y;
		canvas.style.position	 = "absolute";

		var graphics = canvas.getContext("2d");

		graphics.drawImage
		(
			imageToCopyFrom.htmlElement,
			regionPos.x, regionPos.y, // source pos
			regionSize.x, regionSize.y, // source size
			0, 0, // destination pos
			regionSize.x, regionSize.y // destination size
		);

		var imageFromCanvasURL = canvas.toDataURL("image/png");

		var htmlImageFromCanvas = document.createElement("img");
		htmlImageFromCanvas.width = canvas.width;
		htmlImageFromCanvas.height = canvas.height;
		htmlImageFromCanvas.style.position = "absolute";
		htmlImageFromCanvas.src = imageFromCanvasURL;

		var returnValue = Image.buildFromSystemImage
		(
			imageToCopyFrom.name, 
			htmlImageFromCanvas,
			regionSize
		);

		return returnValue;
	}

	ImageHelper.sliceImageIntoTiles = function(imageToSlice, sizeInTiles)
	{
		var returnImages = [];

		var htmlElementLibrary = Globals.Instance.htmlElementLibrary;

		var systemImageToSlice = htmlElementLibrary.getElementByID
		(
			imageToSlice.htmlElementID
		);	

		var imageToSliceSize = imageToSlice.sizeInPixels;
		var tileSize = imageToSliceSize.clone().divide(sizeInTiles);

		var tilePos = new Coords(0, 0);
		var sourcePos = new Coords(0, 0);

		for (var y = 0; y < sizeInTiles.y; y++)
		{
			tilePos.y = y;

			var returnImageRow = [];

			for (var x = 0; x < sizeInTiles.x; x++)
			{							
				tilePos.x = x;

				var canvas		 = document.createElement("canvas");
				canvas.id		 = "tile_" + x + "_" + y;
				canvas.width		 = tileSize.x;
				canvas.height		 = tileSize.y;
				canvas.style.position	 = "absolute";

				var graphics = canvas.getContext("2d");

				sourcePos.overwriteWith(tilePos).multiply(tileSize);

				graphics.drawImage
				(
					systemImageToSlice,
					sourcePos.x, sourcePos.y, // source pos
					tileSize.x, tileSize.y, // source size
					0, 0, // destination pos
					tileSize.x, tileSize.y // destination size
				);

				// browser dependent?
				var imageFromCanvasURL = canvas.toDataURL("image/png");

				var htmlImageFromCanvas = htmlElementLibrary.createElement("img");
				htmlImageFromCanvas.width = canvas.width;
				htmlImageFromCanvas.height = canvas.height;
				htmlImageFromCanvas.style.position = "absolute";
				htmlImageFromCanvas.src = imageFromCanvasURL;

				imageFromCanvas = Image.buildFromSystemImage
				(
					imageToSlice.name + tilePos.toString(),
					htmlImageFromCanvas,
					tileSize
				);

				returnImageRow.push(imageFromCanvas);
			}

			returnImages.push(returnImageRow);
		}

		return returnImages;
	}
}

function InputBinding(key, actionName)
{
	this.key = key;
	this.actionName = actionName;	
}
{
	InputBinding.prototype.action = function()
	{
		return Globals.Instance.universe.defn.actions[this.actionName];
	}
}

function InputHelper()
{
	this.mousePos = new Coords(0, 0);

	this.bindings = [];
	this.keyCodeToBindingLookup = [];

	this.actionsBeingPerformed = [];
}
{
	// instance methods

	InputHelper.prototype.initialize = function()
	{
		document.body.onkeydown = this.processKeyDownEvent.bind(this);
		document.body.onkeyup = this.processKeyUpEvent.bind(this);
		document.body.onmousedown = this.processMouseDownEvent.bind(this);
	}

	InputHelper.prototype.bindingsRegister = function(bindingsToRegister)
	{
		this.bindings = new Array();
		this.keyCodeToBindingLookup = new Array();

		var numberOfBindings = bindingsToRegister.length;
		for (var b = 0; b < numberOfBindings; b++)
		{
			var binding = bindingsToRegister[b];

			this.bindings.push(binding);
			this.keyCodeToBindingLookup[binding.key.systemKeyCode] = binding;
		}
	}

	InputHelper.prototype.updateForTick = function()
	{
		var inputHelper = Globals.Instance.inputHelper;
		var actionsBeingPerformed = inputHelper.actionsBeingPerformed;	

		var actionsToEnd = [];

		for (var i = 0; i < actionsBeingPerformed.length; i++)
		{
			var actionBeingPerformed = actionsBeingPerformed[i];

			actionBeingPerformed.ticksSoFar++;
		}	
	}

	// event handlers

	InputHelper.prototype.processKeyDownEvent = function(event)
	{
		var keycode = "_" + event.which;
		var binding = this.keyCodeToBindingLookup[keycode];
		if (binding != null)
		{
			var action = binding.action();

			var actionsBeingPerformed = this.actionsBeingPerformed;
			if (actionsBeingPerformed[action.name] == null)
			{
				action.ticksSoFar = 0;
				actionsBeingPerformed[action.name] = action;
				actionsBeingPerformed.push(action);
			}
		}
	}

	InputHelper.prototype.processKeyUpEvent = function(event)
	{
		var binding = this.keyCodeToBindingLookup["_" + event.which];

		if (binding != null)
		{
			var action = binding.action();

			var actionsBeingPerformed = this.actionsBeingPerformed;
			if (actionsBeingPerformed[action.name] != null)
			{
				delete actionsBeingPerformed[action.name];

				var indexToDeleteAt = actionsBeingPerformed.indexOf
				(
					action
				);
				actionsBeingPerformed.splice(indexToDeleteAt, 1);
			}
		}
	}

	InputHelper.prototype.processMouseDownEvent = function(event)
	{
		this.mousePos.overwriteWithDimensions(event.x, event.y);

		var keycode = "Mouse";
		var binding = this.keyCodeToBindingLookup[keycode];
		if (binding != null)
		{
			var action = binding.action();

			var actionsBeingPerformed = this.actionsBeingPerformed;
			if (actionsBeingPerformed[action.name] == null)
			{
				action.ticksSoFar = 0;
				actionsBeingPerformed[action.name] = action;
				actionsBeingPerformed.push(action);
			}
		}
	}

}

function InputKey(name, systemKeyCode)
{
	this.name = name;
	this.systemKeyCode = systemKeyCode;
}
{
	function InputKey_Instances()
	{
		if (InputKey.Instances != null) { return; }

		InputKey.Instances = this;

		this._All = 
		[
			new InputKey("A", "_65"),
			new InputKey("B", "_66"),
			new InputKey("C", "_67"),
			new InputKey("D", "_68"),
			new InputKey("E", "_69"),
			new InputKey("F", "_70"),
			new InputKey("G", "_71"),
			new InputKey("H", "_72"),
			new InputKey("I", "_73"),
			new InputKey("J", "_74"),
			new InputKey("K", "_75"),
			new InputKey("K", "_76"),
			new InputKey("M", "_77"),
			new InputKey("N", "_78"),
			new InputKey("O", "_79"),
			new InputKey("P", "_80"),
			new InputKey("Q", "_81"),
			new InputKey("R", "_82"),
			new InputKey("S", "_83"),
			new InputKey("T", "_84"),
			new InputKey("U", "_85"),
			new InputKey("V", "_86"),
			new InputKey("W", "_87"),
			new InputKey("X", "_88"),
			new InputKey("Y", "_89"),
			new InputKey("Z", "_90"),

			new InputKey("BracketClose", "_221"),
			new InputKey("BracketOpen", "_219"),
			new InputKey("Period", "_190"),
		];

		this.systemKeyCodeToKeyLookup = [];
		for (var i = 0; i < this._All.length; i++)
		{
			var key = this._All[i];
			this[key.name] = key;
			this.systemKeyCodeToKeyLookup[key.systemKeyCode] = key;
		}
	}

	new InputKey_Instances();
}

function ItemData()
{
	// todo
}
{}

function ItemDefn(appearance, mass, stackSizeMax, relativeFrequency, initialize, use)
{
	this.appearance = appearance;
	this.mass = mass;
	this.stackSizeMax = stackSizeMax;
	this.relativeFrequency = relativeFrequency;
	this.initialize = initialize;
	this.use = use;

	if (this.initialize == null)
	{
		this.initialize = ItemDefn.InitializeDoNothing;
	}

	if (this.use == null)
	{
		this.use = ItemDefn.UseDoNothing;
	}
}
{
	ItemDefn.InitializeDevice = function(entity, item)
	{
		alert("[UseDevice]");
	}

	ItemDefn.InitializeDoNothing = function()
	{
		// do nothing
	}

	ItemDefn.UseDevice = function(userEntity, deviceEntity, targetEntity)
	{
		deviceEntity.deviceData.use(userEntity, deviceEntity, targetEntity);
	}

	ItemDefn.UseDoNothing = function()
	{
		// do nothing
	}

	ItemDefn.UseEquip = function(entity, item)
	{
		var itemCategoryNames = item.defn().categoryNames;

		var equippableData = entity.equippableData;
		var socketSet = equippableData.equipmentSocketSet;
		var sockets = socketSet.sockets;

		for (var d = 0; d < itemCategoryNames.length; d++)
		{
			var itemCategoryName = itemCategoryNames[d];

			for (var i = 0; i < sockets.length; i++)
			{
				var socket = sockets[i];

				var namesOfCategoriesAllowed = socket.defn.namesOfCategoriesAllowed;

				for (var c = 0; c < namesOfCategoriesAllowed.length; c++)
				{
					var nameOfCategoryAllowed = namesOfCategoriesAllowed[c];

					if (itemCategoryName == nameOfCategoryAllowed)
					{
						socket.itemEquipped = item;
						entity.moverData.controlUpdate(entity);
						return;
					}
				}
			}
		}
	}

}

function KillableData(defn)
{
	this.defn = defn;
	this.integrity = this.defn.integrityMax;
}
{
	KillableData.prototype.integrityAdd = function(amountToAdd)
	{
		this.integrity = NumberHelper.trimValueToRangeMax
		(
			this.integrity + amountToAdd,
			0, 
			this.defn.integrityMax
		);
	}
}

function KillableDefn(integrityMax)
{
	this.integrityMax = integrityMax;
}

function Location(venueName, posInCells)
{
	this.venueName = venueName;
	this.posInCells = posInCells;
	this.heading = 0;
}
{
	Location.prototype.overwriteWith = function(other)
	{
		this.venueName = other.venueName;
		this.posInCells = other.posInCells.clone();
		this.heading = other.heading;
	}

	Location.prototype.venue = function()
	{
		return Globals.Instance.universe.venues[this.venueName];
	}
}

function Map(name, terrains, cellSizeInPixels, cellsAsStrings)
{
	this.name = name;
	this.terrains = terrains;
	this.cellSizeInPixels = cellSizeInPixels;

	this.sizeInCells = new Coords
	(
		cellsAsStrings[0].length, 
		cellsAsStrings.length
	);

	this.sizeInCellsMinusOnes = this.sizeInCells.clone().subtract
	(
		new Coords(1, 1)
	);

	this.sizeInPixels = this.sizeInCells.clone().multiply(this.cellSizeInPixels);

	this.cellIndicesModified = [];

	this.cells = [];

	var cellPos = new Coords(0, 0);
	for (var y = 0; y < this.sizeInCells.y; y++)
	{
		cellPos.y = y;
		for (var x = 0; x < this.sizeInCells.x; x++)
		{
			cellPos.x = x;

			var cellAsChar = cellsAsStrings[cellPos.y][cellPos.x];
			var cellTerrain = this.terrains[cellAsChar];

			var cell = new MapCell(cellTerrain);
			this.cells.push(cell);
		}
	}
}
{
	// static methods

	Map.buildBlank = function(name, terrains, cellSizeInPixels, sizeInCells)
	{
		var cellsAsStrings = [];

		var terrainBlank = terrains[0]; // hack

		for (var y = 0; y < sizeInCells.y; y++)
		{
			var cellRowAsString = "";

			for (var x = 0; x < sizeInCells.x; x++)
			{
				cellRowAsString += terrainBlank.codeChar;
			}

			cellsAsStrings.push(cellRowAsString);
		}

		var returnValue = new Map
		(
			name,
			terrains,
			cellSizeInPixels,
			cellsAsStrings
		);

		return returnValue;
	}

	// instance methods

	Map.prototype.buildCellPosFromIndex = function(cellIndex)
	{
		return new Coords
		(
			cellIndex % this.sizeInCells.x,
			Math.floor(cellIndex / this.sizeInCells.x)
		);
	}

	Map.prototype.cellAtPos = function(cellPos)
	{
		var returnValue = null;

		if (cellPos.isWithinRange(this.sizeInCellsMinusOnes) == true)
		{
			var cellIndex = this.indexOfCellAtPos(cellPos);

			returnValue = this.cells[cellIndex];
		}

		return returnValue;
	}

	Map.prototype.cellAtPos_Set = function(cellPos, cellToSet)
	{
		var cellIndex = this.indexOfCellAtPos(cellPos);
		this.cells[cellIndex] = cellToSet;

		this.cellIndicesModified.push(cellIndex);
	}

	Map.prototype.clone = function()
	{
		var cellsAsStrings = [];

		var cellPos = new Coords(0, 0);
		for (var y = 0; y < map.sizeInCells.y; y++)
		{
			cellPos.y = y;
			var cellRowAsString = "";

			for (var x = 0; x < map.sizeInCells.x; x++)
			{
				cellPos.x = x;
	
				var cell = this.cellAtPos(cellPos);
				var cellTerrain = this.terrains[cellAsChar];
				var terrainChar = terrain.codeChar;
				cellRowAsString += terrainChar;
			}

			cellsAsStrings.push(cellRowAsString);
		}

		return new Map
		(
			this.name,
			this.terrains,
			this.cellSizeInPixels,
			cellsAsStrings
		);
	}

	Map.prototype.copyNCellsAtPositionsToOther = function
	(
		numberOfCellsToCopy, 
		cellPositionsToCopy, 
		other
	)
	{
		for (var i = 0; i < numberOfCellsToCopy; i++)
		{
			var cellPos = cellPositionsToCopy[i];
			var cell = this.cellAtPos(cellPos);
			if (cell != null)
			{
				other.cellAtPos_Set(cellPos, cell);
			}
		}
	}

	Map.prototype.indexOfCellAtPos = function(cellPos)
	{
		return cellPos.y * this.sizeInCells.x + cellPos.x
	}

}

function MapCell(terrain)
{
	this.terrain = terrain;
	this.entitiesPresent = [];
}

function MapTerrain(name, codeChar, costToTraverse, blocksVision, color, image)
{
	this.name = name;
	this.codeChar = codeChar;
	this.costToTraverse = costToTraverse;
	this.blocksVision = blocksVision;
	this.color = color;
	this.image = image;
}

function MediaLoader
(
	objectContainingCallback,
	callbackToRunWhenLoadingComplete,
	items
)
{
	this.objectContainingCallback = objectContainingCallback;
	this.callbackToRunWhenLoadingComplete = callbackToRunWhenLoadingComplete; 
	this.items = items;
	this.items.addLookups("name");
}
{
	MediaLoader.prototype.itemLoaded = function(event)
	{
		this.numberOfItemsLoadedSoFar++;	
		if (this.numberOfItemsLoadedSoFar >= this.items.length)
		{
			this.callbackToRunWhenLoadingComplete.call
			(
				this.objectContainingCallback,
				this
			);
		}
	}

	MediaLoader.prototype.loadItemsAll = function()
	{
		this.numberOfItemsLoadedSoFar = 0;

		for (var i = 0; i < this.items.length; i++)
		{
			var item = this.items[i];
			item.load(this, item);
		}
	}

}

function MediaLoaderItem(name, itemType, path)
{
	this.name = name;
	this.itemType = itemType;
	this.path = path;
}
{
	MediaLoaderItem.prototype.load = function(mediaLoader)
	{
		this.itemType.load(mediaLoader, this);
	}
}

function MediaLoaderItemType(name, load)
{
	this.name = name;
	this.load = load;
}
{
	function MediaLoaderItemType_Instances()
	{
		this.Image = new MediaLoaderItemType
		(
			"Image",
			function(mediaLoader, mediaLoaderItem)
			{

				var htmlElementLibrary = Globals.Instance.htmlElementLibrary;
				var htmlElement = htmlElementLibrary.createElement("img");
				mediaLoaderItem.htmlElement = htmlElement;
				htmlElement.mediaLoaderItem = mediaLoaderItem;
				htmlElement.onload = mediaLoader.itemLoaded.bind(mediaLoader);
				htmlElement.setAttribute("crossOrigin", "anonymous");
				htmlElement.src = mediaLoaderItem.path;
			}
		);
	}

	MediaLoaderItemType.Instances = new MediaLoaderItemType_Instances();
}

function Message(name, textsForLanguages)
{
	this.name = name;
	this.textsForLanguages = textsForLanguages;	
}
{
	Message.getTextForName = function(messageName)
	{
		return Message.Instances._All[messageName].textsForLanguages[0];
	}

	// instances

	function Message_Instances()
	{
		this._All = 
		[
			new Message("NothingHappens", [ "Nothing happens." ])
		];

		this._All.addLookups("name");
	}

	Message.Instances = new Message_Instances();
}

function MoverData(moverDefn)
{
	this.movesThisTurn = moverDefn.movesPerTurn;
	this.demographics = moverDefn.demographics;
	this.locus = new MoverData_Locus();
	this.traits = moverDefn.traits;
	this.skills = moverDefn.skills;
	this.spells = moverDefn.spells;
	this.vitals = new MoverData_Vitals(moverDefn.vitals);
	this.attributes = moverDefn.attributes;
}
{
	// controls

	MoverData.prototype.controlUpdate = function(entity)
	{
		if (this.control == null)
		{
			this.control = new ControlContainer
			(
				"containerMoverData",
				new Coords(0, 0), // pos
				new Coords(220, 240), // size
				[
					new ControlLabel("labelName", new Coords(10, 16), "Name: " + entity.name),
					this.demographics.controlUpdate(entity, new Coords(10, 32)),
					this.traits.controlUpdate(entity, new Coords(10, 48)),
					this.vitals.controlUpdate(entity, new Coords(10, 64)),
					this.locus.controlUpdate(entity, new Coords(10, 80)),
					this.skills.controlUpdate(entity, new Coords(10, 96)),
					this.spells.controlUpdate(entity, new Coords(10, 112)),
					entity.containerData.controlUpdate(entity, new Coords(10, 128)),
				]
			);
		}

		return this.control;				
	}
}

function MoverData_Demographics(species, role, rank)
{
	this.species = species;
	this.role = role;
	this.rank = rank;
}
{
	// controls

	MoverData_Demographics.prototype.controlUpdate = function(entity, pos)
	{
		if (this.control == null)
		{
			this.control = new ControlContainer
			(
				"containerMoverData_Demographics",
				pos,
				new Coords(200, 16), // size
				[
					new ControlLabel
					(
						"labelDemographics", 
						new Coords(10, 10), 
						"Level " + this.rank + " " + this.species + " " + this.role
					),
				]
			);
		}

		return this.control;				
	}
}

function MoverData_Locus()
{
}
{
	// controls

	MoverData_Locus.prototype.controlUpdate = function(entity, pos)
	{
		if (this.control == null)
		{
			this.control = new ControlContainer
			(
				"containerMoverData_Locus",
				pos,
				new Coords(200, 16), // size
				[
					new ControlLabel
					(
						"labelLocus", 
						new Coords(10, 10), 
						"Floor: ^ Turn: ^",
						[
							entity.loc.venue().depth,
							Globals.Instance.universe					
						],
						[
						
							null, 
							"turnsSoFar"
						]
					),
				]
			);
		}

		return this.control;				
	}
}

function MoverData_Skills(skillDefns)
{
	this.skills = [];

	for (var i = 0; i < skillDefns.length; i++)
	{
		var skillDefn = skillDefns[i];
		var skill = new Skill(skillDefn, 0);
		this.skills.push(skill);	
	}
}
{
	MoverData_Skills.prototype.controlUpdate = function(entity, pos)
	{
		if (this.control == null)
		{
			this.control = new ControlContainer
			(
				"containerMoverData_Skills",
				pos,
				new Coords(200, 16), // size
				[
					new ControlLabel("labelSkills", new Coords(10, 10), "Skills"),
				]
			);
		}

		return this.control;				
	}
}

function MoverData_Spells(spells)
{
	this.spells = spells;
}
{
	MoverData_Spells.prototype.controlUpdate = function(entity, pos)
	{
		if (this.control == null)
		{
			this.control = new ControlContainer
			(
				"containerMoverData_Spells",
				pos,
				new Coords(200, 16), // size
				[
					new ControlLabel("labelSpells", new Coords(10, 10), "Spells"),
				]
			);
		}

		return this.control;				
	}
}

function MoverData_Traits(traits)
{
	this.traits = traits;
}
{
	MoverData_Traits.prototype.controlUpdate = function(entity, pos)
	{
		if (this.control == null)
		{
			var textForTraits = "";

			for (var i = 0; i < this.traits.length; i++)
			{
				var trait = this.traits[i];
				
				textForTraits += trait.defn.abbreviation + ": " + trait.rank + " ";
			}			

			this.control = new ControlContainer
			(
				"containerMoverData_Traits",
				pos,
				new Coords(200, 16), // size
				[
					new ControlLabel("labelTraits", new Coords(10, 10), textForTraits),
				]
			);
		}

		return this.control;				
	}
}

function MoverData_Vitals(defn)
{
	this.defn = defn;
	this.energy = defn.energyMax;
	this.satiety = defn.satietyMax;
}
{
	MoverData_Vitals.prototype.addSatietyToMover = function(amountToAdd, moverEntity)
	{
		this.satiety += amountToAdd;

		if (this.satiety <= 0)
		{
			moverEntity.killableData.integrity = 0;
		}
		else if (this.satiety >= moverEntity.defn().moverDefn.vitals.satietyMax)
		{
			// todo
		}

		this.controlUpdate(moverEntity);
	}

	// controls

	MoverData_Vitals.prototype.controlUpdate = function(entity, pos)
	{
		if (this.control == null && pos != null) // hack
		{
			this.control = new ControlContainer
			(
				"containerMoverData_Vitals",
				pos,
				new Coords(200, 16), // size
				[
					new ControlLabel
					(
						"labelHealth", 
						new Coords(10, 10), 
						"Life:^/^",
						[ entity.killableData, entity.killableData.defn ],
						[ "integrity", "integrityMax" ]			
					),

					new ControlLabel
					(
						"labelEnergy", 
						new Coords(60, 10), 
						" Power:" + this.energy + "/" + this.defn.energyMax			
					),
					new ControlLabel
					(
						"labelSatiety", 
						new Coords(120, 10), 
						" Sat: " + this.satiety + "/" + this.defn.satietyMax
					),
				]
			);
		}

		return this.control;				
	}
}

function MoverDefn
(
	difficulty,
	movesPerTurn, 
	demographics, 
	traits, 
	skills, 
	spells,
	vitals, 
	entityDefnCorpse,
	attributeGroups
)
{
	this.difficulty = difficulty;
	this.movesPerTurn = movesPerTurn;
	this.demographics = demographics;
	this.traits = traits;
	this.skills = skills;
	this.spells = spells;
	this.vitals = vitals;
	this.entityDefnCorpse = entityDefnCorpse;
	this.attributeGroups = attributeGroups;

	this.attributeGroups.addLookups("name");
}

function MoverDefn_Vitals(energyMax, satietyMax)
{
	this.energyMax = energyMax;
	this.satietyMax = satietyMax;
}

function MoverGenerator()
{
	// do nothing
}
{
	MoverGenerator.EntityDefn = new EntityDefn
	(
		"MoverGenerator",
		[ "Actor" ], // categoryNames
		// properties
		[
			new ActorDefn("Generate Movers"),
		]
	);
}

function NumberHelper()
{}
{
	NumberHelper.atan3 = function(coordsToFind)
	{
		var returnValue = Math.atan2(coordsToFind.y, coordsToFind.x);

		if (returnValue < 0)
		{
			returnValue += Constants.Tau;
		}

		returnValue /= Constants.Tau;

		return returnValue;
	}

	NumberHelper.trimValueToRangeMax = function(valueToTrim, rangeMin, rangeMax)
	{
		if (valueToTrim > rangeMax)
		{
			valueToTrim = rangeMax;
		}

		return valueToTrim;
	}

	NumberHelper.wrapValueToRangeMax = function(valueToWrap, rangeMax)
	{
		while (valueToWrap < 0)
		{
			valueToWrap += rangeMax;
		}

		while (valueToWrap >= rangeMax)
		{
			valueToWrap -= rangeMax;
		}

		return valueToWrap;
	}
}

function Path(map, startPos, goalPos)
{
	this.map = map;
	this.startPos = startPos;
	this.goalPos = goalPos;
}
{
	Path.prototype.calculate = function()
	{
		var map = this.map;
		var startPos = this.startPos.clone();
		var goalPos = this.goalPos.clone();

		var openList = [];
		var openLookup = [];
		var closedLookup = [];

		var tempPos = Coords.Instances.Temp;

		var startNode = new PathNode
		(
			startPos,
			0,
			tempPos.overwriteWith
			(
				goalPos
			).subtract
			(
				startPos
			).absolute().sumOfXAndY(),
			null
		);

		openList.push(startNode);
		var startIndex = 
			"_" + 
			(
				startNode.cellPos.y  
				* map.sizeInCells.x 
				+ startNode.cellPos.x
			);

		openLookup[startIndex] = startNode;

		while (openList.length > 0)
		{
			var current = openList[0];

			if (current.cellPos.equals(goalPos) == true)
			{	
				this.nodes = new Array();

				while (current != null)
				{
					this.nodes.splice(0, 0, current);
					current = current.prev;
				}
				break;
			}

			openList.splice(0, 1);
			var currentIndex = 
				"_" + 
				(
					current.cellPos.y 
					* map.sizeInCells.x 
					+ current.cellPos.x
				);

			delete openLookup[currentIndex];

			closedLookup[currentIndex] = current;

			var neighbors = this.getNeighborsForNode(map, current, goalPos);

			for (var n = 0; n < neighbors.length; n++)
			{
				var neighbor = neighbors[n];
				var neighborPos = neighbor.cellPos;

				var neighborIndex = 
					"_" + 
					(
						neighborPos.y 
						* map.sizeInCells.x 
						+ neighborPos.x
					);

				if (closedLookup[neighborIndex] == null && openLookup[neighborIndex] == null)
				{
					var i;
					for (i = 0; i < openList.length; i++)
					{
						var nodeFromOpenList = openList[i];
						if (neighbor.costFromStart < nodeFromOpenList.costFromStart)
						{
							break;
						}
					}

					openList.splice(i, 0, neighbor);
					openLookup[neighborIndex] = neighbor;
				}
			}
		}	
	}

	Path.prototype.getNeighborsForNode = function(map, node, goalPos)
	{
		var returnValues = [];
		var originalPos = node.cellPos;
		var neighborPos = originalPos.clone();

		var neighborPositions = [];

		var mapSizeInCellsMinusOnes = map.sizeInCellsMinusOnes;
		var directions = Coords.Instances._DirectionsByHeading;

		for (var i = 0; i < directions.length; i++)
		{
			var direction = directions[i];
			neighborPos.overwriteWith(originalPos).add(direction);

			if (neighborPos.isWithinRange(mapSizeInCellsMinusOnes) == true)
			{
				neighborPositions.push(neighborPos.clone());
			}
		}

		var tempPos = Coords.Instances.Temp;

		for (var i = 0; i < neighborPositions.length; i++)
		{
			var neighborPos = neighborPositions[i];

			var costToTraverse = map.cellAtPos
			(
				neighborPos
			).terrain.costToTraverse;

			costToTraverse *= tempPos.overwriteWith
			(
				neighborPos
			).subtract
			(
				originalPos
			).magnitude();

			var neighborNode = new PathNode
			(
				neighborPos,
				node.costFromStart + costToTraverse,
				costToTraverse + tempPos.overwriteWith
				(
					goalPos
				).subtract
				(
					neighborPos
				).absolute().sumOfXAndY(),
				node
			);

			returnValues.push(neighborNode);
		}

		return returnValues;
	}
}

function PathNode(cellPos, costFromStart, costToGoalEstimated, prev)
{
	this.cellPos = cellPos;
	this.costFromStart = costFromStart;
	this.costToGoalEstimated = costToGoalEstimated;
	this.prev = prev;
}

function PlayerData()
{
	this.venueKnownLookup = [];
}

function PortalData(destinationVenueName, destinationEntityName)
{
	this.destinationVenueName = destinationVenueName;
	this.destinationEntityName = destinationEntityName;
}

function RandomizerLCG(multiplier, addend, modulus, firstRandom)
{
	// "LCG" = "Linear Congruential Generator"

	this.multiplier = multiplier;
	this.addend = addend;
	this.modulus = modulus;
	this.currentRandom = firstRandom;
}
{
	RandomizerLCG.prototype.getNextRandom = function()
	{
		this.currentRandom = 
		(
			(
				this.multiplier 
				* (this.currentRandom * this.modulus)
				+ this.addend
			) 
			% this.modulus
		) 
		/ this.modulus;

		return this.currentRandom;
	}
}

function Range(min, max)
{
	this.min = min;
	this.max = max;
}
{
	Range.prototype.clone = function()
	{
		return new Range(this.min, this.max);
	}

	Range.prototype.overlaps = function(other)
	{
		var returnValue =
		(
			this.min < other.max
			&& this.max > other.min
		);

		return returnValue;
	}

	Range.prototype.random = function()
	{
		return this.min + (this.max - this.min) * Globals.Instance.randomizer.getNextRandom();
	}
}

function RangeSet(ranges)
{
	this.ranges = ranges;
}
{
	RangeSet.prototype.overlaps = function(other)
	{
		var returnValue = false;

		for (var i = 0; i < this.ranges.length; i++)
		{
			var rangeThis = this.ranges[i];

			for (var j = 0; j < other.ranges.length; j++)
			{
				var rangeOther = other.ranges[j];

				if (rangeThis.overlaps(rangeOther) == true)
				{
					returnValue = true;
					i = this.ranges.length;
					break;
				}
			}
		}		

		return returnValue;
	}

	RangeSet.prototype.splitRangesThatSpanPeriod = function(period)
	{
		for (var i = 0; i < this.ranges.length; i++)
		{
			var range = this.ranges[i];

			if (range.min > range.max)
			{
				var rangePart0 = new Range(0, range.max);
				var rangePart1 = new Range(range.min, period);

				this.ranges.splice(i, 1, rangePart0, rangePart1);	
				i++;
			}
		}
	}

	RangeSet.prototype.subtract = function(other)
	{
		for (var i = 0; i < this.ranges.length; i++)
		{
			var rangeThis = this.ranges[i];

			for (var j = 0; j < other.ranges.length; j++)
			{
				var rangeOther = other.ranges[j];

				if (rangeThis.overlaps(rangeOther) == true)
				{
					if (rangeOther.min > rangeThis.min)
					{
						if (rangeOther.max < rangeThis.max)
						{
							var rangeNew = new Range
							(
								rangeOther.max,
								rangeThis.max
							);

							this.ranges.splice
							(
								i + 1, 0, rangeNew
							);
							i++;
						}

						if (rangeThis.min < rangeOther.min)
						{
							rangeThis.max = rangeOther.min;
						}
						else
						{
							this.ranges.splice(i, 1);
							i--;
						}
					}		
					else if (rangeOther.max < rangeThis.max)
					{
						rangeThis.min = rangeOther.max;
					}
					else
					{
						this.ranges.splice(i, 1);
						i--;
					}
				}
			}
		}		
	}
}

function Role(name, ranks, skills)
{
	this.name = name;
	this.ranks = ranks;
	this.skills = skills;
}

function Role_Rank(skillPoints, attributes)
{
	this.skillPoints = skillPoints;
	this.attributes = attributes;
}

function Role_Skill(defn, rankMax)
{
	this.defn = defn;
	this.rankMax = rankMax;
}

function RoomData(pos, size)
{
	this.bounds = new Bounds(pos, size);
	this.roomsConnected = [];
}

function SightHelper()
{
	this.fieldOfView = new FieldOfView();
}
{
	SightHelper.prototype.updateVenueFromCompleteForViewerPosAndRange = function
	(
		venueKnown,
		venueComplete, 
		viewerPos, 
		sightRange
	)
	{
		var fieldOfView = this.fieldOfView;

		fieldOfView.setVenueAndRangeAndViewerPos
		(
			venueComplete, 
			sightRange, 
			viewerPos
		);

		fieldOfView.calculateCellPositionsVisible();

		venueComplete.map.copyNCellsAtPositionsToOther
		(
			fieldOfView.numberOfCellsVisible,
			fieldOfView.cellPositionsVisible,
			mapKnown
		);
	}
}

function Skill(defn, rank)
{
	this.defn = defn;
	this.rank = rank;
}

function SkillDefn(name)
{
	this.name = name;
}

function SpellDefn(name)
{
	this.name = name;
}

function StringHelper()
{}
function StringHelper_Static()
{
	StringHelper.toStringNullable = function(objectToConvertToString)
	{
		if (objectToConvertToString == null)
		{
			objectToConvertToString = "null";
		}

		return objectToConvertToString.toString();
	}
}
StringHelper_Static();

function Trait(defn, rank)
{
	this.defn = defn;
	this.rank = rank;
}

function TraitDefn(name)
{
	this.name = name;
	this.abbreviation = this.name.substr(0, 3);
}

function Universe(name, defn, venues, entityForPlayer)
{
	this.name = name;
	this.defn = defn;
	this.venues = venues;
	this.entityForPlayer = entityForPlayer;

	this.venues.addLookups("name");

	if (this.entityForPlayer == null)
	{
		var venue0 = this.venues[0];
		var portal0 = venue0.entitiesToSpawn[0]; // hack

		this.entityForPlayer = new Entity
		(
			"Player", 
			this.defn.entityDefns["Player"].name, 
			portal0.loc.posInCells.clone()
		);

		this.entityForPlayer.loc.venueName = venue0.name;

		venue0.entitiesToSpawn.splice(0, 0, this.entityForPlayer);
	}

	this.venueNext = this.venues[this.entityForPlayer.loc.venueName];

	this.turnsSoFar = 0;
}
{
	Universe.prototype.update = function()
	{
		if (this.venueNext != null)
		{
			this.venueNext.initialize();
			Globals.Instance.inputHelper.bindingsRegister
			(
				this.venueNext.defn.inputBindings
			);

			this.venueCurrent = this.venueNext;

			this.venueNext = null;
		}

		this.venueCurrent.update();
	}

	// xml

	Universe.prototype.toXmlElement = function()
	{
		return new XmlElement
		(
			this.constructor.name,
			// attributeNameValuePairs
			[	
				[ "name", this.name ],
			],
			// children
			[
				this.defn.toXmlElement()
			]
		);
	}
}

function UniverseDefn
(
	name, 
	actions,
	activityDefns,
	categories, 
	entityDefnGroups, 
	venueDefns,
	venueStructure, 
	buildVenues
)
{
	this.name = name;
	this.actions = actions;
	this.activityDefns = activityDefns;
	this.categories = categories;
	this.entityDefnGroups = entityDefnGroups;
	this.venueDefns = venueDefns;
	this.venueStructure = venueStructure;
	this.buildVenues = buildVenues;

	var entityDefnSets = this.entityDefnGroups.getPropertyWithNameFromEachItem
	(
		"entityDefns"	
	);

	this.entityDefns = ArrayHelper.concatenateArrays
	(
		entityDefnSets
	);

	this.actions.addLookups("name");
	this.activityDefns.addLookups("name");
	this.venueDefns.addLookups("name");
	this.entityDefnGroups.addLookups("name");
	this.entityDefns.addLookups("name");
}
{
	// xml

	UniverseDefn.prototype.toXmlElement = function()
	{
		return new XmlElement
		(
			this.constructor.name,
			// attributeNameValuePairs
			[	
				[ "name", this.name ],
			],
			// children
			[
				new XmlElement("VenueDefns", [], XmlElement.buildManyFromXmlizables(this.venueDefns)),
				new XmlElement("EntityDefnGroups", [], XmlElement.buildManyFromXmlizables(this.entityDefnGroups)),
			]
		);
	}
}

function UniverseDefnVenueStructure(branchRoot)
{
	this.branchRoot = branchRoot;
}

function UniverseDefnVenueStructureBranch
(
	name,
	venueDefnName, 
	startsAfterSibling,
	startOffsetRangeWithinParent, 
	depthRangeInVenues, 
	children
)
{
	this.name = name;
	this.venueDefnName = venueDefnName;
	this.startsAfterSibling = startsAfterSibling;
	this.startOffsetRangeWithinParent = startOffsetRangeWithinParent;
	this.depthRangeInVenues = depthRangeInVenues;
	this.children = children;
	this.venues = [];

	for (var i = 0; i < this.children.length; i++)
	{
		this.children[i].parent = this;
	}
}
{
	UniverseDefnVenueStructureBranch.prototype.buildVenuesAndAddToList = function
	(
		universeDefn,
		venuesSoFar,
		venueDepth
	)
	{
		var venueDefns = universeDefn.venueDefns;
		var entityDefns = universeDefn.entityDefns;

		var venueDefn = venueDefns[this.venueDefnName];
		var numberOfVenuesInBranch = Math.floor(this.depthRangeInVenues.random());

		var indexOfFirstVenueInBranch = venuesSoFar.length;

		for (var i = 0; i < numberOfVenuesInBranch; i++)
		{
			venueDepth++;

			var venue = venueDefn.venueGenerate
			(
				universeDefn, // universeDefn
				venueDefn, 
				venuesSoFar.length, // venueIndex
				venueDepth 
			);

			this.venues.push(venue);

			venuesSoFar.push(venue);
		}

		var venueFirstInBranch = venuesSoFar[indexOfFirstVenueInBranch];

		if (this.parent != null && this.parent.venues.length > 0)
		{
			var venueIndexToBranchFrom = Math.floor(this.startOffsetRangeWithinParent.random());
			var venueToBranchFrom = this.parent.venues[venueIndexToBranchFrom];

			var entityPortalToParentBranch = new Entity
			(
				"StairsUp", 
				universeDefn.entityDefns["StairsUp"].name, 
				venueFirstInBranch.map.sizeInCells.clone().subtract
				(
					Coords.Instances.Ones
				).random(),
				// propertyValues
				[
					new PortalData
					(
						venueToBranchFrom.name,
						"StairsDown" // portalName
					),
				]
			);

			venue.entitiesToSpawn.push(entityPortalToParentBranch);

		}

		for (var i = 0; i < this.children.length; i++)
		{
			var child = this.children[i];
			child.buildVenuesAndAddToList(universeDefn, venuesSoFar, 0);
		}

		return venuesSoFar;
	}
}

function Venue(name, depth, defn, sizeInPixels, map, entities)
{
	this.name = name;
	this.depth = depth;
	this.defn = defn;
	this.sizeInPixels = sizeInPixels;
	this.map = map;
	this.entities = [];

	this.sizeInPixelsHalf = this.sizeInPixels.clone().divideScalar(2);

	this.entitiesByCategoryName = [];
	for (var c = 0; c < this.defn.categoriesKnown.length; c++)
	{
		var categoryName = this.defn.categoriesKnown[c].name;

		this.entitiesByCategoryName.push(categoryName);
		this.entitiesByCategoryName[categoryName] = [];
	}

	this.entitiesToSpawn = [];
	this.entitiesToRemove = [];

	for (var i = 0; i < entities.length; i++)
	{
		var entity = entities[i];
		entity.loc.venueName = this.name;
		this.entitiesToSpawn.push(entity);
	}

	this.camera = new Camera
	(
		"Camera",
		Camera.ViewSizeStandard
	);

	this.camera.entity.loc.venueName = this.name;

	this.entitiesToSpawn.push(this.camera.entity);
}
{
	// instance methods

	Venue.prototype.entitiesInCategoryPresentAtCellPos = function(categoryName, cellPosToCheck)
	{
		var returnEntities = [];

		var entitiesInCategory = this.entitiesByCategoryName[categoryName];

		if (entitiesInCategory != null)
		{
			for (var i = 0; i < entitiesInCategory.length; i++)
			{
				var entity = entitiesInCategory[i];
				if (entity.loc.posInCells.equals(cellPosToCheck) == true)
				{
					returnEntities.splice(0, 0, entity);
				}
			}
		}

		return returnEntities;
	}

	Venue.prototype.entitySpawn = function(entityToSpawn)
	{
		entityToSpawn.loc.venueName = this.name;

		this.entities.push(entityToSpawn);		
		this.entities[entityToSpawn.name] = entityToSpawn;

		var categoriesAll = Globals.Instance.universe.defn.categories;

		var entityCategoryNames = entityToSpawn.defn().categoryNames;
		for (var c = 0; c < entityCategoryNames.length; c++)
		{
			var entityCategoryName = entityCategoryNames[c];
			var entityCategory = categoriesAll[entityCategoryName];

			var entityListForCategory = this.entitiesByCategoryName[entityCategoryName];

			if (entityListForCategory != null)
			{
				entityListForCategory.push(entityToSpawn);

				if (entityCategory.initializeEntityForVenue != null)
				{
					entityCategory.initializeEntityForVenue(entityToSpawn, this);
				}
			}
		}
	}

	Venue.prototype.initialize = function()
	{
		var categoriesKnown = this.defn.categoriesKnown;

		for (var b = 0; b < this.entities.length; b++)
		{
			var entity = this.entities[b];

			var entityCategoryNames = entity.defn().categoryNames;
			for (var c = 0; c < entityCategoryNames.length; c++)
			{
				var entityCategoryName = entityCategoryNames[c];
				var entityCategory = categoriesKnown[entityCategoryName];

				if (entityCategory.initializeEntityForVenue != null)
				{
					entityCategory.initializeEntityForVenue(entity, this);
				}
			}
		}
	}

	Venue.prototype.update = function()
	{
		this.update_EntitiesToSpawn();

		var player = Globals.Instance.universe.entityForPlayer;
		var venueKnown = player.playerData.venueKnownLookup[this.name];

		if (venueKnown != null)
		{
			Globals.Instance.displayHelper.drawVenue(venueKnown);
			Globals.Instance.displayHelper.drawControl
			(
				venueKnown.controlUpdate()
			);
		}

		var numberOfCategories = this.entitiesByCategoryName.length;

		var categoriesAll = Globals.Instance.universe.defn.categories;

		for (var c = 0; c < numberOfCategories; c++)
		{
			var categoryName = this.entitiesByCategoryName[c];
			var category = categoriesAll[categoryName];

			if (category.updateEntityForVenue != null)
			{
				var entitiesInCategory = this.entitiesByCategoryName[categoryName];

				var numberOfEntitiesInCategory = entitiesInCategory.length;

				for (var b = 0; b < numberOfEntitiesInCategory; b++)
				{
					var entity = entitiesInCategory[b];
					category.updateEntityForVenue(entity, this);
				}
			}
		}

		this.update_Collidables();

		this.update_EntitiesToRemove();
	}

	Venue.prototype.update_EntitiesToRemove = function()
	{
		var categoriesAll = Globals.Instance.universe.defn.categories;

		for (var i = 0; i < this.entitiesToRemove.length; i++)
		{
			var entityToRemove = this.entitiesToRemove[i];

			if (entityToRemove.collidableData != null)
			{
				var entitiesInCell = entityToRemove.collidableData.mapCellOccupied.entitiesPresent;
				entitiesInCell.splice
				(
					entitiesInCell.indexOf(entityToRemove),
					1
				);
			}

			this.entities.splice(this.entities.indexOf(entityToRemove), 1);
			delete this.entities[entityToRemove.name];

			var entityCategoryNames = entityToRemove.defn().categoryNames;
			for (var c = 0; c < entityCategoryNames.length; c++)
			{
				var entityCategoryName = entityCategoryNames[c];
				var entityCategory = categoriesAll[entityCategoryName];
				var entitiesForCategory = this.entitiesByCategoryName[entityCategoryName];
				entitiesForCategory.splice
				(
					entitiesForCategory.indexOf(entityToRemove), 
					1
				);
			}
		}

		this.entitiesToRemove.length = 0;
	}

	Venue.prototype.update_EntitiesToSpawn = function()
	{
		for (var i = 0; i < this.entitiesToSpawn.length; i++)
		{
			var entityToSpawn = this.entitiesToSpawn[i];
			this.entitySpawn(entityToSpawn);
		}

		this.entitiesToSpawn.length = 0;
	}

	Venue.prototype.update_Collidables = function()
	{
		var categories = Globals.Instance.universe.defn.categories;

		var emplacements = this.entitiesByCategoryName[categories.Emplacement.name];
		var enemies = this.entitiesByCategoryName[categories.Enemy.name];
		var items = this.entitiesByCategoryName[categories.Item.name];
		var players = this.entitiesByCategoryName[categories.Player.name]
		var portals = this.entitiesByCategoryName[categories.Portal.name];
		var projectiles = this.entitiesByCategoryName[categories.Projectile.name];

		var collisionHelper = Globals.Instance.collisionHelper;

		var collisionSets = 
		[
			collisionHelper.findCollisionsBetweenEntitiesInSets
			(
				players, 
				emplacements
			),

			collisionHelper.findCollisionsBetweenEntitiesInSets
			(
				players, 
				enemies
			),

			collisionHelper.findCollisionsBetweenEntitiesInSets
			(
				players, 
				items
			),

			collisionHelper.findCollisionsBetweenEntitiesInSets
			(
				players, 
				portals
			),

			collisionHelper.findCollisionsBetweenEntitiesInSets
			(
				enemies,
				projectiles
			),
		];

		for (var s = 0; s < collisionSets.length; s++)
		{
			var collisions = collisionSets[s];

			var numberOfCollisions = collisions.length;
			for (var c = 0; c < numberOfCollisions; c++)
			{
				var collision = collisions[c];

				var numberOfEntities = collision.entities.length;

				for (var b0 = 0; b0 < numberOfEntities; b0++)
				{
					var entityThis = collision.entities[b0];
					for (var b1 = b0 + 1; b1 < numberOfEntities; b1++)
					{
						var entityOther = collision.entities[b1];

						collisionHelper.collideEntities
						(
							collision, entityThis, entityOther
						);
						collisionHelper.collideEntities
						(
							collision, entityOther, entityThis
						);
					}
				}
			}
		}
	}

	// controls

	Venue.prototype.controlUpdate = function()
	{
		if (this.control == null)
		{
			var entityForPlayer = Globals.Instance.universe.entityForPlayer;
			this.control = new ControlContainer
			(
				"containerVenue",
				new Coords(10, 10), // pos
				new Coords(220, 240), // size
				// children
				[
					entityForPlayer.moverData.controlUpdate(entityForPlayer),
				]
			);
		}		

		return this.control;
	}
}

function VenueDefn(name, categoriesKnown, terrains, inputBindings, venueGenerate)
{
	this.name = name;
	this.categoriesKnown = categoriesKnown;
	this.terrains = terrains;
	this.inputBindings = inputBindings;
	this.venueGenerate = venueGenerate;
}
{
	// xml

	VenueDefn.prototype.toXmlElement = function()
	{
		return new XmlElement
		(
			this.constructor.name,
			// attributeNameValuePairs
			[	
				[ "name", this.name ],
			],
			// children
			[
				// todo
			]
		);
	}
}

function VisualOffset(visual, offset)
{
	this.visual = visual;
	this.offset = offset;
}
{
	VisualOffset.prototype.drawToGraphicsAtPos = function(graphics, drawPos)
	{	
		this.visual.drawToGraphicsAtPos
		(
			graphics,
			drawPos.clone().add(this.offset)
		);
	}
}

function VisualSet(name, visuals)
{
	this.name = name;
	this.visuals = visuals;
}
{
	VisualSet.prototype.cloneAsVisual = function()
	{
		return this; // todo
	}

	VisualSet.prototype.drawToGraphicsAtPos = function(graphics, drawPos)
	{
		for (var i = 0; i < this.visuals.length; i++)
		{
			var visual = this.visuals[i];

			visual.drawToGraphicsAtPos(graphics, drawPos);
		}
	}

	VisualSet.prototype.updateForVenue = function(entity, venue)
	{
		// do nothing
	}
}

function XmlElement(tagName, attributeNameValuePairs, children)
{
	this.tagName = tagName;
	this.attributeNameValuePairs = attributeNameValuePairs;
	this.children = children;
}
{
	XmlElement.buildManyFromXmlizables = function(xmlizablesToBuildFrom)
	{
		var returnValues = [];

		for (var i = 0; i < xmlizablesToBuildFrom.length; i++)
		{
			var xmlizable = xmlizablesToBuildFrom[i];

			var xmlElement = xmlizable.toXmlElement();

			returnValues.push(xmlElement);
		}

		return returnValues;	
	}

	XmlElement.prototype.toString = function()
	{
		var returnValue = "<" + this.tagName;

		for (var i = 0; i < this.attributeNameValuePairs.length; i++)
		{
			var attributeNameValuePair = this.attributeNameValuePairs[i];
			returnValue += 
				" "
				+ attributeNameValuePair[0]
				+ "='" 
				+ attributeNameValuePair[1]
				+ "'";
		}

		if (this.children.length == 0)
		{
			returnValue += "/>";
		}
		else
		{
			returnValue += ">";

			for (var i = 0; i < this.children.length; i++)
			{
				var child = this.children[i];

				returnValue += child.toString();
			}

			returnValue += "</" + this.tagName +">";
		}

		return returnValue;
	}
}

// data

// hack
function AnimationDefnSetFake(image)
{
	this.image = image;
}
{
	AnimationDefnSetFake.buildFromImage = function(image)
	{
		return new AnimationDefnSetFake(image);
	}

	AnimationDefnSetFake.prototype.toRun = function()
	{
		return this.image;
	}
}

function DemoData()
{
	// do nothing
}
{
	DemoData.buildActions = function()
	{
		// Action.perform() declarations

		var actionEmplacement_Use_Perform = function(actor, action)
		{
			var loc = actor.loc;
			var venue = loc.venue();
			var posInCells = actor.loc.posInCells;
			var usablesPresentInCell = venue.entitiesInCategoryPresentAtCellPos
			(
				"Portal",
				posInCells
			);

			if (usablesPresentInCell.length == 0)
			{
				return;
			}

			var usableToUse = usablesPresentInCell[0];
			var costToUse = 1;

			if (actor.moverData.movesThisTurn < costToUse)
			{
				return;
			}

			actor.moverData.movesThisTurn -= costToUse;

			var portal = usableToUse;

			var portalData = portal.portalData;
			var destinationVenueName = portalData.destinationVenueName;
			var destinationEntityName = portalData.destinationEntityName;
			var universe = Globals.Instance.universe;

			var destinationVenue = universe.venues[destinationVenueName];
			if (destinationVenue != null)
			{
				destinationVenue.initialize();
				destinationVenue.update();				

				var entities = destinationVenue.entities;
				var destinationEntity = entities[destinationEntityName];
				if (destinationEntity != null)
				{
					actor.loc.venue().entitiesToRemove.push(actor);
					destinationVenue.entitiesToSpawn.push(actor);
					actor.loc.posInCells.overwriteWith
					(
						destinationEntity.loc.posInCells
					);

					Globals.Instance.universe.venueNext = destinationVenue;

					actor.moverData.controlUpdate(actor);
				}
			}
		}

		var actionItem_DropSelected_Perform = function(actor, action)
		{
			var loc = actor.loc;
			var venue = loc.venue();
			var posInCells = loc.posInCells;
			var itemsPresentInCell = venue.entitiesInCategoryPresentAtCellPos
			(
				"Item",
				posInCells
			);

			var containerData = actor.containerData;
			var itemToDrop = containerData.itemSelected;
			var costToDrop = 1;

			if (itemToDrop != null && actor.moverData.movesThisTurn >= costToDrop)
			{
				actor.moverData.movesThisTurn -= costToDrop;
				actor.containerData.dropItem(actor, itemToDrop);
			}
		}

		var actionItem_PickUp_Perform = function(actor, action)
		{
			var loc = actor.loc;
			var venue = loc.venue();
			var posInCells = actor.loc.posInCells;

			var entitiesPresentAtCellPos = venue.map.cellAtPos(posInCells).entitiesPresent;

			for (var i = 0; i < entitiesPresentAtCellPos.length; i++)
			{
				var entityPresent = entitiesPresentAtCellPos[i];
				if (entityPresent.defn().categoryNames.indexOf("Item") >= 0)
				{
					var itemToPickUp = entityPresent;
					var costToPickUp = 1;

					if (actor.moverData.movesThisTurn >= costToPickUp)
					{
						actor.moverData.movesThisTurn -= costToPickUp;

						actor.containerData.pickUpItem(actor, itemToPickUp);
					}
				}
			}
		}

		var actionItem_SelectAtOffset_Perform = function(actor, action)
		{
			var containerData = actor.containerData;
			var itemsHeld = containerData.items;

			if (itemsHeld.length == 0)
			{
				return;
			}

			var itemSelected = containerData.itemSelected;

			var indexOfItemSelected;

			if (itemSelected == null)
			{
				indexOfItemSelected = 0;
			}			
			else
			{
				var indexOfItemSelected = itemsHeld.indexOf
				(
					itemSelected
				);

				var indexOffset = this.argumentForPerform;

				indexOfItemSelected += indexOffset;

				indexOfItemSelected = NumberHelper.wrapValueToRangeMax
				(
					indexOfItemSelected,	
					itemsHeld.length		
				);
			}

			containerData.itemSelected = itemsHeld[indexOfItemSelected];		

			actor.moverData.controlUpdate(actor);
		}

		var actionItem_TargetSelected_Perform = function(actor, action)
		{
			var containerData = actor.containerData;
			containerData.itemTargeted = containerData.itemSelected;
			actor.moverData.controlUpdate(actor);
		}

		var actionItem_UseSelected_Perform = function(actor, action)
		{
			var itemToUse = actor.containerData.itemSelected;

			if (itemToUse != null)
			{
				var movesToUse = 1; // todo

				if (actor.moverData.movesThisTurn >= movesToUse)
				{
					actor.moverData.movesThisTurn -= movesToUse;

					itemToUse.defn.itemDefn.use(actor, itemToUse, actor);
				}
			}
		}

		var actionMove_Perform = function(actor, action)
		{		
			var directionToMove = action.argumentForPerform;

			if (directionToMove.magnitude() == 0)
			{
				return;
			}

			var actorLoc = actor.loc;
			var venue = actorLoc.venue();

			var posInCellsDestination = actorLoc.posInCells.clone().add
			(
				directionToMove
			);

			var cellDestination = venue.map.cellAtPos(posInCellsDestination);

			var entitiesInCellDestination = cellDestination.entitiesPresent;

			var isDestinationAccessible = true;

			for (var b = 0; b < entitiesInCellDestination.length; b++)
			{
				var entityInCell = entitiesInCellDestination[b];

				if (entityInCell.collidableData.defn.blocksMovement == true)
				{
					isDestinationAccessible = false;
				}

				if (entityInCell.defn().categoryNames.indexOf("Mover") >= 0)
				{	
					isDestinationAccessible = false;
		
					var costToAttack = 1; // todo
					actor.moverData.movesThisTurn -= costToAttack;
	
					// todo - Calculate damage.
					var damageInflicted = DiceRoll.roll("1d6");
	
					var entityDefns = Globals.Instance.universe.defn.entityDefns;
					var defnsOfEntitiesToSpawn = [];
	
					Font.spawnMessageFloater
					(
						"Dagger", 
						"-" + damageInflicted, 
						actorLoc
					);
	
					if (damageInflicted > 0)
					{	
						entityInCell.killableData.integrityAdd
						(
							0 - damageInflicted
						);
	
						if (entityInCell.killableData.integrity <= 0)
						{
							defnsOfEntitiesToSpawn.push 
							(
								entityInCell.defn().moverDefn.entityDefnCorpse
							);
						}
						else
						{
							defnsOfEntitiesToSpawn.push
							(
								entityDefns["Blood"]
							);
						}
					}
	
					for (var i = 0; i < defnsOfEntitiesToSpawn.length; i++)
					{
						var defnOfEntityToSpawn = defnsOfEntitiesToSpawn[i];
	
						var entityToSpawn = new Entity
						(
							defnOfEntityToSpawn.name + "_Spawned",
							defnOfEntityToSpawn.name,
							posInCellsDestination
						);
	
						venue.entitiesToSpawn.push
						(
							entityToSpawn
						);
					}
				}
			}

			if (isDestinationAccessible == true)
			{
				var costToTraverse = cellDestination.terrain.costToTraverse;
				if (costToTraverse <= actor.moverData.movesThisTurn)
				{
					actor.moverData.movesThisTurn -= costToTraverse;

					var cellDeparted = actor.collidableData.mapCellOccupied;
					var entitiesInCellDeparted = cellDeparted.entitiesPresent;
					entitiesInCellDeparted.splice
					(
						entitiesInCellDeparted.indexOf(actor),
						1
					);

					entitiesInCellDestination.push(actor);
					actor.collidableData.mapCellOccupied = cellDestination;

					actor.loc.posInCells.overwriteWith
					(
						posInCellsDestination
					);
				}
			}

		}

		var actionWait_Perform = function(actor, action)
		{
			actor.moverData.movesThisTurn = 0;
		}

		// directions

		var directions = new Coords_Instances()._DirectionsByHeading;

		// actions

		var actionEmplacement_Use = new Action
		(
			"Use Emplacement", 1, actionEmplacement_Use_Perform
		);

		var actionItem_DropSelected = new Action
		(
			"Drop Selected Item", 1, actionItem_DropSelected_Perform
		);

		var actionItem_PickUp = new Action
		(
			"Pick Up Item", 1, actionItem_PickUp_Perform
		);

		var actionItem_SelectNext = new Action
		(
			"Select Next Item", null, actionItem_SelectAtOffset_Perform, 1
		);

		var actionItem_SelectPrev = new Action
		(
			"Select Previous Item", null, actionItem_SelectAtOffset_Perform, -1
		); 

		var actionItem_TargetSelected= new Action
		(
			"Target Selected Item", 1, actionItem_TargetSelected_Perform
		);

		var actionItem_UseSelected 	= new Action
		(
			"Use Selected Item", 1, actionItem_UseSelected_Perform
		);

		var actionMoveE = new Action
		(
			"Move East", null, actionMove_Perform, directions[0]
		);

		var actionMoveSE = new Action
		(
			"Move Southeast", null, actionMove_Perform, directions[1]
		);

		var actionMoveS = new Action
		(
			"Move South", null, actionMove_Perform, directions[2]
		);

		var actionMoveSW = new Action
		(
			"Move Southwest", null, actionMove_Perform, directions[3]
		);

		var actionMoveW = new Action
		(
			"Move West", null, actionMove_Perform, directions[4]
		);

		var actionMoveNW = new Action
		(
			"Move Northwest", null, actionMove_Perform, directions[5]
		);

		var actionMoveN = new Action
		(
			"Move North", null, actionMove_Perform, directions[6]
		);

		var actionMoveNE = new Action
		(
			"Move Northeast", null, actionMove_Perform, directions[7]
		);

		var actionWait = new Action("Wait", null, actionWait_Perform);

		var returnValues = 
		[
			actionEmplacement_Use,
			actionItem_DropSelected,
			actionItem_PickUp,
			actionItem_SelectNext,
			actionItem_SelectPrev,
			actionItem_TargetSelected,
			actionItem_UseSelected,
			actionMoveE,
			actionMoveSE,
			actionMoveS,
			actionMoveSW,
			actionMoveW,
			actionMoveNW,
			actionMoveN,
			actionMoveNE,
			actionWait,
		];

		// hack
		returnValues._MovesByHeading = 
		[
			actionMoveE, 
			actionMoveSE,
			actionMoveS, 
			actionMoveSW,
			actionMoveW,
			actionMoveNW, 
			actionMoveN,		
			actionMoveNE,
		];

		returnValues.addLookups("name");

		return returnValues;
	}

	DemoData.buildActivityDefns = function()
	{
		var activityDefnDoNothing = new ActivityDefn
		(
			"Do Nothing",

			// initialize
			function(actor, activity) 
			{
				// do nothing
			},

			// perform
			function(actor, activity)
			{
				// do nothing
			}
		);

		var activityDefnGenerateMovers = new ActivityDefn
		(
			"Generate Movers",

			// initialize 
			function(actor, activity) 
			{
				// do nothing
			},

			// perform
			function(actor, activity)
			{
				var actorLoc = actor.loc;
				var venue = actorLoc.venue();
	
				var agentsInVenue = venue.entitiesByCategoryName["Mover"];
	
				var numberOfAgentsDesired = 5;
	
				if (agentsInVenue.length < numberOfAgentsDesired)
				{
					var chanceOfSpawnPerTurn = 1; // hack - actually per tick
	
					if (Math.random() < chanceOfSpawnPerTurn)
					{		
						var difficultyMax = 1; // hack
	
						var universe = Globals.Instance.universe;
						var entityDefnsForAgents = universe.defn.entityDefnGroups["Agents"].entityDefns;
					
						var entityDefnForAgentToSpawn = null;
						var entityDefnIndex = Math.floor(Math.random() * entityDefnsForAgents.length);
						while (entityDefnForAgentToSpawn == null)
						{	
							var entityDefn = entityDefnsForAgents[entityDefnIndex];
							var moverDefn = entityDefn.moverDefn;
							if (moverDefn.difficulty <= difficultyMax)
							{
								entityDefnForAgentToSpawn = entityDefn;
							}
	
							entityDefnIndex++;
							if (entityDefnIndex >= entityDefnsForAgents.length)
							{
								entityDefnIndex = 0;
							}
						}
	
	
						var posToSpawnAt = venue.map.sizeInCells.clone().random();
	
						var entityForAgent = new Entity
						(
							entityDefnForAgentToSpawn.name + "0",
							entityDefnForAgentToSpawn.name,
							posToSpawnAt
						);				
			
						venue.entitiesToSpawn.push(entityForAgent);	
					}
				}
			}
		);

		var activityDefnMoveRandomly = new ActivityDefn
		(
			"Move Randomly",

			// initialize 
			function(actor, activity) 
			{
				// do nothing
			},

			// perform 
			function(actor, activity)
			{
				// hack
				var actionsMoves = Globals.Instance.universe.defn.actions._MovesByHeading;

				var numberOfDirectionsAvailable = actionsMoves.length;
				var directionIndexRandom = Math.floor
				(
					numberOfDirectionsAvailable
					* Globals.Instance.randomizer.getNextRandom() // Math.random()
				);

				var actionMoveInRandomDirection = actionsMoves[directionIndexRandom];

				actor.actorData.actions.push(actionMoveInRandomDirection);
			}
		);

		var activityDefnMoveTowardPlayer = new ActivityDefn
		(
			"Move Toward Player",

			// initialize 
			function(actor, activity) 
			{
				// do nothing
			},

			// perform 
			function(actor, activity)
			{
				if (actor.moverData.movesThisTurn <= 0)
				{
					return;
				}

				var actorLoc = actor.loc;
				var venue = actorLoc.venue();
				var players = venue.entitiesByCategoryName["Player"];

				if (players != null && players.length > 0)
				{
					var player = players[0];

					var path = new Path
					(
						venue.map, 
						actorLoc.posInCells, 
						player.loc.posInCells
					);

					path.calculate();

					if (path.nodes.length < 2)
					{
						return;
					}

					var pathNode1 = path.nodes[1];

					var directionsToPathNode1 = pathNode1.cellPos.clone().subtract
					(
						actor.loc.posInCells
					).directions();

					var heading = Heading.fromCoords(directionsToPathNode1);

					// hack
					var actionsMoves = Globals.Instance.universe.defn.actions._MovesByHeading;
					var actionMoveInDirection = actionsMoves[heading];	

					actor.actorData.actions.push
					(
						actionMoveInDirection
					);
				}
			}
		);

		var activityDefnUserInputAccept = new ActivityDefn
		(
			"Accept User Input",

			// initialize 
			function(actor, activity) 
			{},

			// perform 
			function(actor, activity)
			{
				var inputHelper = Globals.Instance.inputHelper;
				var actionsFromInput = inputHelper.actionsBeingPerformed;
				var actionsFromActor = actor.actorData.actions; 

				for (var a = 0; a < actionsFromInput.length; a++)
				{
					var action = actionsFromInput[a];
					var ticksToHold = 
					(
						action.ticksToHold == null 
						? action.ticksSoFar // hold forever
						: action.ticksToHold
					);

					if (action.ticksSoFar <= ticksToHold)
					{
						actionsFromActor.push(action);
					}
				}	
			}
		);

		var returnValues = 
		[
			activityDefnDoNothing,
			activityDefnGenerateMovers,
			activityDefnMoveRandomly,
			activityDefnMoveTowardPlayer,
			activityDefnUserInputAccept,
		];

		returnValues.addLookups("name");

		return returnValues;
	}

	DemoData.buildCategories = function()
	{
		var Actor = new Category
		(
			"Actor", 
			// construct
			null,
			null, // collide
			// initialize
			function(entity, venue)
			{
				var actorData = new ActorData();
				entity.actorData = actorData;

				actorData.actions = [];

				var activity = new Activity
				(
					entity.defn().actorDefn.activityDefnNameInitial,
					null
				);

				actorData.activity_Set(entity, activity);
			},
			// update
			function(entity, venue)
			{
				entity.actorData.activity_Get().perform(entity);

				var entityActions = entity.actorData.actions;

				for (var a = 0; a < entityActions.length; a++)
				{
					var action = entityActions[a];
					action.perform(entity, action);
				}

				entityActions.length = 0;
			}
		);

		var Camera = new Category("Camera", null, null, null, null);

		var Collidable = new Category
		(
			"Collidable", 
			// construct
			null,
			// collide
			function(entity)
			{
				// todo
			},			
			// initialize
			function(entity, venue)
			{		
				var collidableData = new CollidableData
				(
					entity.defn().collidableDefn
				);

				var map = venue.map;
				var mapCellOccupied = map.cellAtPos(entity.loc.posInCells);
				mapCellOccupied.entitiesPresent.push(entity);
				collidableData.mapCellOccupied = mapCellOccupied;

				entity.collidableData = collidableData;
			}, 
			// update
			function(entity, venue)
			{
				// todo
			},
			// finalize
			function(entity, venue)
			{						
				var collidableData = entity.collidableData;
				var entitiesPresentInCellOccupied = collidableData.mapCellOccupied;
				entitiesPresentInCellOccupied.splice
				(
					entitiesPresentInCellOccupied.indexOf(entity),
					1
				);
			}		
		);

		var Container = new Category
		(
			"Container", 
			// construct
			null,
			null, // collide
			// initialize
			function(entity, venue)
			{
				entity.containerData = new ContainerData();
			},
			null, // update
			null // finalize
		);

		var Device = new Category
		(
			"Device", 
			// construct
			null, // collide
			// initialize
			function(entity, venue)
			{
				entity.deviceData = new DeviceData
				(
					entity.defn().deviceDefn.chargesMax
				);
			},
			null, // update
			null // finalize
		);

		var Drawable = new Category
		(
			"Drawable", 
			null, // construct
			null, // collide
			// initialize
			function(entity, venue)
			{			
				entity.drawableData = new DrawableData
				(
					true // isVisible
				); 

				var drawableData = entity.drawableData;	
				var drawableDefn = entity.defn().drawableDefn;
				drawableData.visual = drawableDefn.visual.cloneAsVisual();
			},
			// update
			function(entity, venue)
			{
				entity.drawableData.visual.updateForVenue
				(
					entity, venue
				);
			},
			null // finalize
		);

		var Dynamic = new Category
		(
			"Dynamic", 
			null, // construct
			null, // collide
			null, // initialize
			// update
			function(entity, venue)
			{
				entity.dynamicData = new DynamicData(entity.defn().dynamicDefn);
				var dynamicData = entity.dynamicData;
				entity.loc.posInCells.add(dynamicData.vel);
			}		 
		);

		var Effectable = new Category
		(
			"Effectable", 
			null, // construct
			null, // collide
			// initialize
			function(entity)
			{
				entity.effectData = new EffectData();
			},
			// update
			function(entity, venue)
			{
				// todo
			}		 
		);

		var Emplacement = new Category("Emplacement", null, null, null, null);		

		var Enemy = new Category("Enemy", null, null, null, null);

		var Ephemeral = new Category
		(
			"Ephemeral", 
			null, // construct
			null, // collide 
			// initialize
			function(entity, venue) 
			{ 
				entity.ephemeralData = new EphemeralData
				(
					entity.defn().ephemeralDefn
				); 
			}, 
			// update
			function(entity, venue) 
			{ 
				var ephemeralData = entity.ephemeralData;

				ephemeralData.ticksToLive--; 
				if (ephemeralData.ticksToLive <= 0)
				{
					venue.entitiesToRemove.push(entity);
				}
			}
		);

		var Equippable = new Category
		(
			"Equippable", 
			null, // construct
			null, // collide
			// initialize
			function(entity)
			{
				entity.equippableData = new EquippableData
				(
					entity.defn().equippableDefn
				);
			},
			null // update
		);

		var Item = new Category
		(
			"Item", 
			null, // construct
			null, // collide
			// initialize
			function(entity)
			{
				entity.itemData = new ItemData();
			},
			null
		);

		var Killable = new Category
		(
			"Killable", 
			null, // construct
			null, // collide
			// initialize 
			function(entity, venue) 
			{ 
				entity.killableData = new KillableData
				(
					entity.defn().killableDefn
				);
			},
			// update
			function(entity, venue) 
			{  
				var killableData = entity.killableData;

				if (killableData.ticksToLive != null) 
				{ 
					killableData.ticksToLive--;

					if (killableData.ticksToLive <= 0)
					{
						venue.entitiesToRemove.push(entity); 
					}
				} 

				if (killableData.integrity <= 0) 
				{ 
					venue.entitiesToRemove.push(entity); 
				} 
			} 
		);

		var Mover = new Category
		(
			"Mover", 
			null, // construct
			null, // collide
			// initialize
			function(entity)
			{
				entity.moverData = new MoverData(entity.defn().moverDefn);
			},
			// update
			function(entity, venue)
			{
				var entityLoc = entity.loc;

				entityLoc.posInCells.trimToRange
				(
					venue.map.sizeInCellsMinusOnes
				);
			}
		);

		var Player = new Category
		(
			"Player",
			null, // construct
			// collide
			function(entityThis, entityOther)
			{
				// todo
			},
			// initialize
			function(entity, venue) 
			{
				entity.playerData = new PlayerData();

				entity.moverData.movesThisTurn = 0;

				var venueKnownLookup = entity.playerData.venueKnownLookup;
				var venueKnown = venueKnownLookup[venue.name];
				if (venueKnown == null)
				{
					var mapComplete = venue.map;

					mapKnown = Map.buildBlank
					(
						mapComplete.name + "_Known",
						mapComplete.terrains,
						mapComplete.cellSizeInPixels,
						mapComplete.sizeInCells
					);

					venueKnown = new Venue
					(
						venue.name + "_Known",
						venue.depth,
						venue.defn,
						venue.sizeInPixels,
						mapKnown,
						[] // entities 
					);

					venueKnownLookup[venue.name] = venueKnown;

					var categoryName = "Drawable";
					var entitiesNotYetVisible = venue.entitiesByCategoryName[categoryName];
					for (var i = 0; i < entitiesNotYetVisible.length; i++)
					{
						var entity = entitiesNotYetVisible[i];
						entity.drawableData.isVisible = false;
					}

					Globals.Instance.sightHelper.updateVenueFromCompleteForViewerPosAndRange
					(
						venueKnown,
						venue,
						entity.loc.posInCells,
						8 //sightRange
					);

				}
			},
			// update
			function(entity, venue) 
			{
				if (entity.moverData.movesThisTurn <= 0)
				{
					var vitals = entity.moverData.vitals.addSatietyToMover(-1, entity);

					var categoryName = "Mover";
					var moversToRecharge = venue.entitiesByCategoryName[categoryName];
					for (var i = 0; i < moversToRecharge.length; i++)
					{
						var mover = moversToRecharge[i];
						mover.moverData.movesThisTurn = mover.defn().moverDefn.movesPerTurn;
					}

					Globals.Instance.universe.turnsSoFar++;	

					var venueKnown = entity.playerData.venueKnownLookup[venue.name];

					Globals.Instance.sightHelper.updateVenueFromCompleteForViewerPosAndRange
					(
						venueKnown,						
						venue, // venueComplete
						entity.loc.posInCells,
						8 //sightRange
					);

					entity.moverData.controlUpdate(entity);
				}
			}
		);

		var Portal = new Category("Portal", null, null, null, null);

		var Projectile = new Category
		(
			"Projectile",
			null, // construct
			// collide
			function(entityThis, entityOther)
			{
				var entityOtherCategoryNames = entityOther.defn().categoryNames;
				if (entityOtherCategoryNames.indexOf("Enemy") >= 0)
				{
					entityOther.killableData.integrity = 0;
				}
			},
			null,
			null // update
		);

		var returnValues = 
		[
			Actor,
			Camera,
			Collidable,
			Container,
			Device,
			Drawable,
			Dynamic,
			Effectable,
			Emplacement,
			Enemy,
			Ephemeral,
			Equippable,
			Item,
			Killable,
			Mover,
			Player,
			Portal,
			Projectile,

			new Category("Headwear"),
			new Category("Neckwear"),
			new Category("Shirt"),
			new Category("EntityArmor"),
			new Category("Cloak"),
			new Category("Glove"),
			new Category("Footwear"),
			new Category("Shield"),

			new Category("Armor"),
			new Category("Food"),
			new Category("Potion"),
			new Category("Ring"),
			new Category("Scroll"),
			new Category("Spellbook"),
			new Category("Tool"),
			new Category("Wand"),
			new Category("Weapon"),
			new Category("Ammunition"),
		];

		returnValues.addLookups("name");

		return returnValues;
	}

	DemoData.buildEntityDefnGroups = function(images, activityDefns, categories)
	{
		// entityDefns

		var emplacements = DemoData.buildEntityDefnGroups_Emplacements(images, categories);

		var items = DemoData.buildEntityDefnGroups_Items(images, categories);

		var movers = DemoData.buildEntityDefnGroups_Movers(images, activityDefns, categories);

		var cameras = new EntityDefnGroup("Cameras", 0, [ Camera.EntityDefn ]);

		var returnValues = ArrayHelper.concatenateArrays
		([
			[ cameras ],
			[ emplacements ],
			items,
			[ movers ],
		]);

		return returnValues;
	}

	DemoData.buildEntityDefnGroups_Emplacements = function(images, categories)
	{
		//var categories = Category.Instances;
		var animation = AnimationDefnSetFake.buildFromImage;

		var emplacements = 
		[
			"Collidable",
			"Drawable", 
			"Emplacement",
		];

		var sizeInPixels = images["Floor"].sizeInPixels;

		var entityDefns = 
		[
			new EntityDefn
			(
				"Blood", 
				ArrayHelper.concatenateArrays
				([
					emplacements, 
					[ "Ephemeral" ],
				]),
				[ 
					CollidableDefn.Instances.Clear, 
					new DrawableDefn(animation(images["Blood"]).toRun(), sizeInPixels),
					new EphemeralDefn(30),
				] 
			),

			new EntityDefn
			(
				"Door", 
				emplacements, 
				[ 
					CollidableDefn.Instances.Concealing,
					new DrawableDefn(animation(images["Door"]).toRun(), sizeInPixels) ,
				]
			),

			new EntityDefn
			(
				"Gravestone", 
				emplacements, 
				[ 
					CollidableDefn.Instances.Clear,
					new DrawableDefn(animation(images["Gravestone"]).toRun(), sizeInPixels) 
				]
			),

			new EntityDefn
			(
				"StairsDown",
				[
					"Collidable",
					"Drawable", 
					"Emplacement",
					"Portal",
				],
				[
					CollidableDefn.Instances.Clear,
					new DrawableDefn(animation(images["StairsDown"]).toRun(), sizeInPixels),
				]
			),

			new EntityDefn
			(
				"StairsUp",
				[
					"Collidable",
					"Drawable", 
					"Emplacement",
					"Portal",
				],
				[
					CollidableDefn.Instances.Clear,
					new DrawableDefn(animation(images["StairsUp"]).toRun(), sizeInPixels),
				]
			),
		];

		var returnValue = new EntityDefnGroup
		(
			"Emplacements",
			1, // relativeFrequency
			entityDefns
		);

		return returnValue;
	}

	DemoData.buildEntityDefnGroups_Items = function(images, categories)
	{
		// convenience variables

		var animation = AnimationDefnSetFake.buildFromImage;

		var categoriesCommon = 
		[
			"Collidable",
			"Drawable",
			"Item",
		];

		var sizeInPixels = images["Floor"].sizeInPixels;

		var itemPropertiesNoStack = new ItemDefn
		(
			"[Appearance]",
			1, // mass
			1, // stackSizeMax
			1, // relativeFrequency
			null, // initialize
			null // use
		);

		var itemPropertiesStandard = new ItemDefn
		(
			"[Appearance]",
			1, // mass
			999, // stackSizeMax
			1, // relativeFrequency
			null, // initialize
			null // use
		);

		var effectDefnDoNothing = new EffectDefn
		(
			"Do Nothing", 
			function(targetEntity) 
			{ 
				// do nothing
			} 
		);

		var effectDoNothing = new Effect(effectDefnDoNothing);

		var entityDefnSets = [];

		var methodsToRun = 
		[ 
			DemoData.buildEntityDefns_Items_Containers,
			DemoData.buildEntityDefns_Items_Food,
			DemoData.buildEntityDefns_Items_Potions,
			DemoData.buildEntityDefns_Items_Rings,
			DemoData.buildEntityDefns_Items_Scrolls,
			DemoData.buildEntityDefns_Items_Spellbooks,
			DemoData.buildEntityDefns_Items_Wands,
			DemoData.buildEntityDefns_Items_Weapons,
			DemoData.buildEntityDefns_Items_Armor,
			DemoData.buildEntityDefns_Items_Tools,
			DemoData.buildEntityDefns_Items_Stones,
		];

		var itemDefnGroups = [];

		for (var i = 0; i < methodsToRun.length; i++)
		{
			var itemDefnGroup = methodsToRun[i]
			(
				images, 
				animation,
				categories,
				categoriesCommon,
				sizeInPixels,
				itemPropertiesNoStack,
				itemPropertiesStandard,
				effectDoNothing,
				[] // entityDefnSets
			);

			itemDefnGroups.push(itemDefnGroup);

			entityDefnSets.push(itemDefnGroup.entityDefns);
		}

		return itemDefnGroups;
	}

	DemoData.buildEntityDefns_Items_Containers = function
	(
		images, 
		animation,
		categories,
		categoriesCommon,
		sizeInPixels,
		itemPropertiesNoStack,
		itemPropertiesStandard,
		effectDoNothing,
		entityDefnSets
	)
	{
		entityDefnSets.push
		([	
			new EntityDefn
			(
				"Chest",
				[
					"Collidable",
					"Container",
					"Drawable", 
					"Item",
				],
				[
					itemPropertiesNoStack,
					new DrawableDefn(animation(images["Chest"]).toRun(), sizeInPixels),
				]
			),
		]);

		var returnValue = new EntityDefnGroup("Containers", 1, entityDefnSets[0]);

		return returnValue;
	}

	DemoData.buildEntityDefns_Items_Food = function
	(
		images, 
		animation,
		categories,
		categoriesCommon,
		sizeInPixels,
		itemPropertiesNoStack,
		itemPropertiesStandard,
		effectDoNothing,
		entityDefnSets
	)
	{
		// items - foods

		var namesOfFoods = 
		[
			"Eucalyptus Leaf",			
			"Apple",
			"Orange",
			"Pear",
			"Melon",
			"Banana",
			"Carrot",
			"Sprig of Wolfsbane",
			"Garlic Clove",
			"Slime Mold",
			"Royal Jelly",
			"Cream Pie",
			"Candy Bar",
			"Fortune Cookie",
			"Pancake",
			"Lembas Wafer",
			"Cram Ration",
			"Food Ration",
			"K Ration",
			"C Ration",
			"Tin",
		];

		var effectNourish = new Effect
		(
			new EffectDefn
			(
				"Nourish", 
				function(targetEntity) 
				{ 
					targetEntity.moverData.vitals.addSatietyToMover(1000, targetEntity);
					targetEntity.moverData.controlUpdate(targetEntity);
				} 
			)
		);

		var entityDefnSetFoods = [];

		for (var i = 0; i < namesOfFoods.length; i++)
		{
			var name = namesOfFoods[i];

			var entityDefn = new EntityDefn
			(
				name,
				ArrayHelper.concatenateArrays
				([
					categoriesCommon,
					[
						"Device",
						"Food",
					]
				]), 
				[
					CollidableDefn.Instances.Clear,
					new DeviceDefn
					(
						1, // chargesMax	
						true, // consumedWhenAllChargesUsed
						// effectsToApply
						[
							effectNourish
						] 
					),
					new DrawableDefn(animation(images[name]).toRun(), sizeInPixels),
					new ItemDefn
					(
						name, 
						1, // mass
						1, // stackSizeMax,
						1, // relativeFrequency
						ItemDefn.InitializeDevice,
						ItemDefn.UseDevice
					)
				]				
			);

			entityDefnSetFoods.push(entityDefn);
		}

		entityDefnSets.push(entityDefnSetFoods);

		return new EntityDefnGroup("Food", 1, entityDefnSets[0]);
	}

	DemoData.buildEntityDefns_Items_Potions = function
	(
		images, 
		animation,
		categories,
		categoriesCommon,
		sizeInPixels,
		itemPropertiesNoStack,
		itemPropertiesStandard,
		effectDoNothing,
		entityDefnSets
	)
	{
		// items - magic - potions

		var ED = EffectDefn;

		var effectMessageNotImplemented = new EffectDefn
		(
			"Display Not Implemented Message", 
			function(actingEntity, targetEntity) 
			{
				Font.spawnMessageFloater
				(
					actingEntity.defn().name, 
					"NOT IMPLEMENTED - " + actingEntity.defn().name, 
					targetEntity.loc
				);

				targetEntity.controlUpdate();
			}
		);	 

		var namesAndEffectDefnsOfPotions = 
		[
			[ "Acid" 		, new ED( null, function(ab, tb) { tb.moverData.integrityAdd(-30); tb.moverData.controlUpdate(tb); } ) ],
			[ "Blindness" 		, effectMessageNotImplemented ],
			[ "Booze" 		, effectMessageNotImplemented ],
			[ "Enlightenment" 	, effectMessageNotImplemented ],
			[ "Confusion" 		, effectMessageNotImplemented ],
			[ "Fruit Juice" 	, new ED( null, function(ab, tb) { tb.moverData.vitals.addSatietyToMover(100, targetEntity); tb.moverData.controlUpdate(targetEntity); } ) ],
			[ "Gain Ability" 	, new ED( null, function(ab, tb) { tb.moverData.traits.strength += 1; tb.moverData.controlUpdate(tb); } ) ],
			[ "Gain Energy" 	, new ED( null, function(ab, tb) { tb.moverData.vitals.energy += 100; tb.moverData.controlUpdate(tb); } ) ],
			[ "Gain Level" 		, new ED( null, function(ab, tb) { tb.moverData.demographics.level += 1; tb.moverData.controlUpdate(tb); } ) ],
			[ "Healing" 		, new ED( null, function(ab, tb) { tb.killableData.integrityAdd(10); tb.moverData.controlUpdate(tb); } ) ],
			[ "Healing Extra" 	, new ED( null, function(ab, tb) { tb.killableData.integrityAdd(30); tb.moverData.controlUpdate(tb); } ) ],
			[ "Healing Full" 	, new ED( null, function(ab, tb) { tb.killableData.integrityAdd(1000); tb.moverData.controlUpdate(tb); } ) ],
			[ "Invisibility" 	, effectMessageNotImplemented ],
			[ "Levitation" 		, effectMessageNotImplemented ],
			[ "Monster Detection" 	, effectMessageNotImplemented ],
			[ "Paralysis" 		, effectMessageNotImplemented ],
			[ "Object Detection" 	, effectMessageNotImplemented ],
			[ "Oil" 		, effectMessageNotImplemented ],
			[ "Polymorph" 		, effectMessageNotImplemented ],
			[ "Restore Ability" 	, effectMessageNotImplemented ],
			[ "See Invisible" 	, effectMessageNotImplemented ],
			[ "Sickness" 		, new ED( null, function(ab, tb) { tb.killableData.integrityAdd(-20); tb.moverData.controlUpdate(tb); } ) ],
			[ "Sleeping" 		, effectMessageNotImplemented ],
			[ "Speed" 		, effectMessageNotImplemented ],
			[ "Water" 		, effectMessageNotImplemented ],
		];

		var appearances = 
		[
			"Ruby","Pink","Orange","Yellow",
			"Emerald","Dark Green","Sky Blue","Cyan",
			"Brilliant Blue","Magenta","Purple-Red","Puce",
			"Milky","Swirly","Bubbly","Smoky",
			"Cloudy","Effervescent","Black","Golden",
			"Brown","Fizzy","Dark","White",
			"Murky", "Clear",
		];

		var entityDefnSetPotions = [];

		for (var i = 0; i < namesAndEffectDefnsOfPotions.length; i++)
		{
			var appearanceIndex = Math.floor
			(
				Globals.Instance.randomizer.getNextRandom()
				* appearances.length
			);
			var appearance = appearances[appearanceIndex] + " Potion";
			appearances.splice(appearanceIndex, 1);

			var potionData = namesAndEffectDefnsOfPotions[i];
			var name = potionData[0];
			var effectDefn = potionData[1];
			effectDefn.name = name;

			var entityDefn = new EntityDefn
			(
				"Potion of " + name, 
				ArrayHelper.concatenateArrays
				([
					categoriesCommon,
					[
						"Potion",
						"Device",
					]
				]), 
				[
					CollidableDefn.Instances.Clear,
					new DeviceDefn
					(
						1, // chargesMax	
						true, // consumedWhenAllChargesUsed
						// effectsToApply
						[
							new Effect(effectDefn)
						] 
					),
					//new DrawableDefn(animation(images[appearance]).toRun(), sizeInPixels)
					new DrawableDefn(images[appearance], sizeInPixels),
					new ItemDefn
					(
						appearance,
						1, // mass
						1, // stackSizeMax,
						1, // relativeFrequency
						ItemDefn.InitializeDevice,
						ItemDefn.UseDevice
					)
				]				
			);

			entityDefnSetPotions.push(entityDefn);
		}

		entityDefnSets.push(entityDefnSetPotions);

		return new EntityDefnGroup("Potions", 1, entityDefnSets[0]);
	}

	DemoData.buildEntityDefns_Items_Rings = function
	(
		images, 
		animation,
		categories,
		categoriesCommon,
		sizeInPixels,
		itemPropertiesNoStack,
		itemPropertiesStandard,
		effectDoNothing,
		entityDefnSets
	)
	{
		// items - magic - rings

		var namesOfRings = 
		[
			"Adornment",		// 0
			"Aggravate Monster",
			"Conflict",
			"Free Action",
			"Gain Constitution",
			"Gain Strength",	// 5
			"Hunger",
			"Increase Accuracy",
			"Increase Damage",
			"Invisibility",
			"Levitation",		// 10
			"Polymorph",
			"Polymorph Control",
			"Protection",
			"Protection from Shape Changers",
			"Regeneration",		// 15
			"Resist Cold",
			"Resist Shock",
			"Resist Fire",
			"Resist Posion",
			"Searching",		// 20
			"See Invisible",
			"Slow Digestion",
			"Stealth",
			"Sustain Ability",
			"Teleport",		// 25
			"Teleport Control",
			"Warning",		// 27
		];

		appearances = 
		[
			"Pearl","Iron","Twisted","Steel",
			"Wire","Engagement","Shiny","Bronze",
			"Brass","Copper","Silver","Gold",
			"Wooden","Granite","Opal","Clay",
			"Coral","Black Onyx","Moonstone","Tiger Eye",
			"Jade","Agate","Topaz","Sapphire",
			"Ruby","Diamond","Ivory","Emerald",
		];

		var entityDefnSetRings = [];

		for (var i = 0; i < namesOfRings.length; i++)
		{
			var appearanceIndex = Math.floor
			(
				Globals.Instance.randomizer.getNextRandom()
				* appearances.length
			);
			var appearance = appearances[appearanceIndex] + " Ring";
			appearances.splice(appearanceIndex, 1);

			entityDefnSetRings.push
			(
				new EntityDefn
				(
					"Ring of " + namesOfRings[i], 
					ArrayHelper.concatenateArrays
					([
						categoriesCommon,
						[ "Ring" ],
					]), 
					[
						CollidableDefn.Instances.Clear,
						new DrawableDefn(animation(images[appearance]).toRun(), sizeInPixels),
						new ItemDefn
						(
							appearance,
							1, // mass
							1, // stackSizeMax
							1, // relativeFrequency
							ItemDefn.InitializeDoNothing,
							ItemDefn.UseEquip
						),
					]				
				)
			);
		}

		entityDefnSets.push(entityDefnSetRings);

		return new EntityDefnGroup("Rings", 1, entityDefnSets[0]);
	}

	DemoData.buildEntityDefns_Items_Scrolls = function
	(
		images, 
		animation,
		categories,
		categoriesCommon,
		sizeInPixels,
		itemPropertiesNoStack,
		itemPropertiesStandard,
		effectDoNothing,
		entityDefnSets
	)
	{
		// items - magic - scrolls

		var namesOfScrolls = 
		[
			"Amnesia", // 0
			"Blank", 
			"Charging", 
			"Confuse Monster", 
			"Create Monster", 
			"Destroy Armor",  // 5
			"Detect Food", 
			"Detect Gold", 
			"Earth", 
			"Enchant Armor", 
			"Enchant Weapon", // 10
			"Fire", 
			"Genocide", 
			"Identify", 
			"Light", 
			"Mapping", // 15
			"Punishment", 
			"Remove Curse", 
			"Scare Monster", 
			"Stinking Cloud", 
			"Taming", // 20
			"Teleport", // 21
		];

		appearances = 
		[
			"Andova Begarin", "Daiyen Fooels", "Duam Xnaht", "Eblib Yloh", 
			"Elam Ebow", "Foobie Bletch", "Garven Deh", "Hackem Muche", 
			"Juyed Awk Yacc", "Kernod Wel", "Kirje", "Lep Gex Ven Zea", 
			"NR 9", "Pratyavayah", "Prirutsenie", "Read Me", 
			"Temov", "Tharr", "Ve Forbryderne", "Velox Neb", 
			"Venzar Borgavve", "Verr Yed Horre", "Xixaxa Xoxaxa Xuxaxa", "Yum Yum", 
			"Zelgo Mer", 
		];

		var entityDefnSetScrolls = [];

		for (var i = 0; i < namesOfScrolls.length; i++)
		{
			var appearanceIndex = Math.floor
			(
				Globals.Instance.randomizer.getNextRandom()
				* appearances.length
			);
			var appearance = "Scroll Titled '" + appearances[appearanceIndex] + "'";			
			appearances.splice(appearanceIndex, 1);

			var entityDefn = new EntityDefn
			(
				"Scroll of " + namesOfScrolls[i], 
				ArrayHelper.concatenateArrays
				([
					categoriesCommon,
					[
						"Device",
						"Scroll",
					],
				]), 
				[
					CollidableDefn.Instances.Clear,
					new DeviceDefn
					(
						1, // chargesMax	
						true, // consumedWhenAllChargesUsed
						// effectsToApply
						[
							effectDoNothing
						] 
					),
					new DrawableDefn(animation(images[appearance]).toRun(), sizeInPixels),
					new ItemDefn
					(
						appearance,
						1, // mass
						1, // stackSizeMax
						1, // relativeFrequency
						ItemDefn.InitializeDevice,
						ItemDefn.UseDevice
					),
				]				
			);

			entityDefnSetScrolls.push(entityDefn);
		}

		entityDefnSets.push(entityDefnSetScrolls);

		var returnValue = new EntityDefnGroup("Scrolls", 1, entityDefnSetScrolls);

		return returnValue;
	}

	DemoData.buildEntityDefns_Items_Spellbooks = function
	(
		images, 
		animation,
		categories,
		categoriesCommon,
		sizeInPixels,
		itemPropertiesNoStack,
		itemPropertiesStandard,
		effectDoNothing,
		entityDefnSets
	)
	{
		var namesOfSpellbooks = 
		[
			// attack

			"Force Bolt", // 0
			"Drain Life", 
			"Magic Missile", 
			"Cone of Cold", 
			"Fireball", 
			"Finger of Death",  // 5

			// clerical

			"Protection", 
			"Create Monster", 
			"Remove Curse", 
			"Create Familiar", 
			"Turn Undead", // 10

			// divination

			"Detect Monsters", 
			"Light", 
			"Detect Food", 
			"Clairvoyance", 
			"Detect Unseen", // 15
			"Identify", 
			"Detect Treasure", 
			"Magic Mapping",

			// enchantment
			"Sleep",
			"Confuse Monster", // 20
			"Slow Monster",
			"Cause Fear",
			"Charm Monster",

			// escape

			"Jumping",
			"Haste", // 25
			"Invisibility",
			"Levitation",
			"Teleport Away",

			// healing

			"Healing",
			"Cure Blindness", // 30
			"Cure Sickness",
			"Extra Healing",
			"Stone to Flesh",
			"Restore Ability",

			// matter

			"Knock", // 35
			"Wizard Lock",
			"Dig",
			"Polymorph",
			"Cancellation", // 39
 		];

		var appearances = 
		[
			"Parchment","Vellum","Ragged","Dogeared",
			"Mottled","Stained","Cloth","Leather",
			"White","Pink","Red","Orange",
			"Yellow","Velvet","Light Green","Dark Green",
			"Turquoise","Cyan","Light Blue","Dark Blue",
			"Indigo","Magenta","Purple","Violet",
			"Tan","Plaid","Light Brown","Dark Brown",
			"Gray","Wrinkled","Dusty","Bronze",
			"Copper","Silver","Gold","Glittering",
			"Shining","Dull","Thin","Thick",
		];

		var entityDefnSetSpellbooks = [];

		for (var i = 0; i < namesOfSpellbooks.length; i++)
		{
			var nameOfSpellbook = namesOfSpellbooks[i];

			var appearanceIndex = Math.floor
			(
				Globals.Instance.randomizer.getNextRandom()
				* appearances.length
			);
			var appearance = appearances[appearanceIndex] + " Spellbook";
			appearances.splice(appearanceIndex, 1);

			var effectLearnSpell = new Effect
			(
				new EffectDefn
				(
					"Learn Spell: " + nameOfSpellbook, 
					function(targetEntity) 
					{ 
						var spellToAdd = new SpellDefn("[Spell]");
						var spellsKnown = targetEntity.moverData.spells.spells;

						var isSpellAlreadyKnown = false;
						for (var i = 0; i < spellsKnown.length; i++)
						{
							if (spellsKnown[i].name == spellToAdd.name)
							{
								isSpellAlreadyKnown = true;
								break;
							}
						}

						if (isSpellAlreadyKnown == false)
						{
							spellsKnown.push(spellToAdd);
						}
					} 
				)
			);

			var entityDefn = new EntityDefn
			(
				"Spellbook of " + nameOfSpellbook, 
				ArrayHelper.concatenateArrays
				([
					categoriesCommon,
					[ 
						"Device",
						"Spellbook", 
					],
				]), 
				[
					CollidableDefn.Instances.Clear,
					new DeviceDefn(10, true, [ effectLearnSpell ]),
					new DrawableDefn(animation(images[appearance]).toRun(), sizeInPixels),
					new ItemDefn
					(
						appearance,
						1, // mass
						1, // stackSizeMax
						1, // relativeFrequency
						ItemDefn.InitializeDevice,
						ItemDefn.UseDevice
					)
				]				
			);

			entityDefnSetSpellbooks.push(entityDefn);
		}

		entityDefnSets.push(entityDefnSetSpellbooks);

		var returnValue = new EntityDefnGroup("Spellbooks", 1, entityDefnSets[0]);

		return returnValue;
	}

	DemoData.buildEntityDefns_Items_Wands = function
	(
		images, 
		animation,
		categories,
		categoriesCommon,
		sizeInPixels,
		itemPropertiesNoStack,
		itemPropertiesStandard,
		effectDoNothing,
		entityDefnSets
	)
	{
		var effectMessage = new Effect
		(
			new EffectDefn
			(
				"Display a Message", 
				function(actingEntity, targetEntity) 
				{
					Font.spawnMessageFloater
					(
						actingEntity.defn().name, 
						"NOT IMPLEMENTED - " + actingEntity.defn().name, 
						targetEntity.loc
					);

					targetEntity.controlUpdate();
				}
			)
		);

		var effectProjectileSpawn = new Effect
		(
			new EffectDefn
			(
				"Spawn Projectile", 
				function(actingEntity, targetEntity) 
				{
					var universe = Globals.Instance.universe;
					var loc = targetEntity.loc;
					var venue = loc.venue();

					var entityForProjectile = new Entity
					(
						"Projectile0",
						universe.defn.entityDefns["Rock"].name,
						loc.posInCells.clone()
					);

					venue.entitiesToSpawn.push(entityForProjectile);

					targetEntity.controlUpdate();
				}
			)
		);

		var effectTeleport = new Effect
		(
			new EffectDefn
			(
				"Teleport", 
				function(actingEntity, targetEntity) 
				{
					var loc = targetEntity.loc;

					var teleportPos = null;
					while (teleportPos == null)
					{
						var map = loc.venue().map;
						teleportPos = map.sizeInCells.clone().random();

						var cellToTeleportTo = map.cellAtPos(teleportPos);
						if (cellToTeleportTo.terrain.costToTraverse > 1)
						{
							teleportPos = null;									
						}
					}
					loc.posInCells.overwriteWith(teleportPos);

					targetEntity.controlUpdate();
					targetEntity.moverData.controlUpdate(targetEntity);
				}
			)
		);

		var wandDatas = 
		[
			[ "Cancelling", 	effectMessage ], // 0
			[ "Cold", 		effectProjectileSpawn ], 
			[ "Create Monster", 	effectMessage ], 
			[ "Death",		effectProjectileSpawn ], 
			[ "Digging",		effectTeleport ],
			[ "Enlightenment",	effectMessage ], // 5
			[ "Fire",		effectProjectileSpawn ],
			[ "Light", 		effectMessage ],
			[ "Lightning", 		effectProjectileSpawn ],
			[ "Locking",		effectMessage ],
			[ "Make Invisible",	effectMessage ], // 10
			[ "Magic Missile",	effectProjectileSpawn ],
			[ "Nothing",		effectMessage ],
			[ "Opening",		effectMessage ],
			[ "Polymorph",		effectMessage ],
			[ "Probing",		effectMessage ], // 15
			[ "Secret Door Detection", effectMessage ], 
			[ "Sleep",		effectMessage ],
			[ "Slow Monster",	effectMessage ],
			[ "Speed Monster", 	effectMessage ],
			[ "Striking",		effectProjectileSpawn ], // 20
			[ "Teleport",		effectTeleport ], 
			[ "Turn Undead",	effectMessage ],
			[ "Wishing",		effectMessage ], // 23
		];

		appearances = 
		[
			"Glass","Balsa","Crystal","Maple",
			"Pine","Oak","Ebony","Marble",
			"Tin","Brass","Copper","Silver",
			"Platinum","Iridium","Zinc","Aluminum",
			"Uranium","Iron","Steel","Hexagonal",
			"Short","Runed","Long","Curved",
			"Forked","Spiked","Jeweled",
		];

		var entityDefnSetWands = [];

		for (var i = 0; i < wandDatas.length; i++)
		{
			var wandData = wandDatas[i];
			var name = wandData[0];
			var effect = wandData[1];

			var appearanceIndex = Math.floor
			(
				Globals.Instance.randomizer.getNextRandom(),
				appearances.length
			);
			var appearance = appearances[appearanceIndex] + " Wand";
			appearances.splice(appearanceIndex, 1);

			entityDefnSetWands.push
			(
				new EntityDefn
				(
					"Wand of " + name, 
					ArrayHelper.concatenateArrays
					([
						categoriesCommon,
						[ 
							"Device",
							"Wand",
						],
					]), 
					[
						CollidableDefn.Instances.Clear,
						new DeviceDefn
						(
							10, // chargesMax	
							false, // consumedWhenAllChargesUsed
							// effectsToApply
							[
								effect
							] 
						),
						new DrawableDefn(animation(images[appearance]).toRun(), sizeInPixels),
						new ItemDefn
						(
							appearance,
							1, // mass
							1, // stackSizeMax
							1, // relativeFrequency
							ItemDefn.InitializeDevice,
							ItemDefn.UseDevice
						),
					]				
				)
			);
		}

		entityDefnSets.push(entityDefnSetWands);

		return new EntityDefnGroup("Wands", 1, entityDefnSets[0]);

	}

	DemoData.buildEntityDefns_Items_MagicTools = function
	(
		images, 
		animation,
		categories,
		categoriesCommon,
		sizeInPixels,
		itemPropertiesNoStack,
		itemPropertiesStandard,
		effectDoNothing,
		entityDefnSets
	)
	{
		// todo
	}

	DemoData.buildEntityDefns_Items_Weapons = function
	(
		images, 
		animation,
		categories,
		categoriesCommon,
		sizeInPixels,
		itemPropertiesNoStack,
		itemPropertiesStandard,
		effectDoNothing,
		entityDefnSets
	)
	{
		var namesAndAppearancesOfWeapons = 
		[
			[ "Arrow", "Arrow" ],
			[ "Battle Axe", "Battle Axe" ],
			[ "Bow", "Bow" ],
			[ "Bow2", "Bow2" ],
			[ "Bow3", "Bow3" ],
			[ "Bow4", "Bow4" ],
			[ "Sling", "Sling" ],
			[ "Crossbow", "Crossbow" ],
			[ "Crossbow Bolt", "Crossbow Bolt" ],
			[ "Dagger", "Dagger" ],
			[ "Elven Dagger", "Runed Dagger" ],
			[ "Hand Axe", "Hand Axe" ],
			[ "Knife", "Knife" ],
			[ "Orcish Dagger", "Crude Dagger" ],
			[ "Polearm1", "Polearm1" ],
			[ "Silver Arrow", "Silver Arrow" ],
			[ "Sword", "Sword" ],
		];

		var entityDefnSetWeapons = [];

		for (var i = 0; i < namesAndAppearancesOfWeapons.length; i++)
		{
			var nameAndAppearance = namesAndAppearancesOfWeapons[i];
			var name = nameAndAppearance[0];
			var appearance = nameAndAppearance[1];

			var entityDefn = new EntityDefn
			(
				name, 
				ArrayHelper.concatenateArrays
				([
					categoriesCommon,
					[ "Weapon" ]
				]), 
				[
					CollidableDefn.Instances.Clear,
					new DrawableDefn(animation(images[name]).toRun(), sizeInPixels),
					new ItemDefn
					(
						appearance,
						1, // mass
						1, // stackSizeMax
						1, // relativeFrequency
						null, // initialize
						ItemDefn.UseEquip// use
					),
				]
			);

			entityDefnSetWeapons.push(entityDefn);
		};

		entityDefnSets["Group_Weapons"] = entityDefnSetWeapons;
		entityDefnSets.push(entityDefnSetWeapons);

		return new EntityDefnGroup("Weapons", 1, entityDefnSets[0]);
	}

	DemoData.buildEntityDefns_Items_Armor = function
	(
		images, 
		animation,
		categories,
		categoriesCommon,
		sizeInPixels,
		itemPropertiesNoStack,
		itemPropertiesStandard,
		effectDoNothing,
		entityDefnSets
	)
	{
		var headwear = categories["Headwear"];
		var entityArmor = categories["EntityArmor"];
		var shirt = categories["Shirt"];
		var cloak = categories["Cloak"];
		var footwear = categories["Footwear"];
		var shield = categories["Shield"];

		var namesAndCategoriesOfArmor = 
		[
			[ "Elven Leather Helmet",headwear ],
			[ "Orcish Helmet", 	headwear ],
			[ "Dwarvish Helmet", 	headwear ],
			[ "Black Hat", 		headwear ],
			[ "Cornuthaum", 	headwear ],
			[ "Dunce Cap",  	headwear ],
			[ "Cooking Pot",  	headwear ],
			[ "Plumed Helmet",  	headwear ],
			[ "Etched Helmet",  	headwear ],
			[ "Crested Helmet", 	headwear ],
			[ "Visored Helmet",  	headwear ],

			[ "Plate Mail", 	entityArmor ],
			[  "Crystal Plate Mail", entityArmor ],
			[ "Bronze Plate Mail", 	entityArmor ],
			[ "Armor1", 		entityArmor ],
			[ "Armor2", 		entityArmor ],
			[ "Elven Mithril Shirt", entityArmor],
			[ "Dwarven Mithril Shirt", entityArmor],
			[ "Armor3", 		entityArmor ],
			[ "Orcish Chain Mail", 	entityArmor ],
			[ "Armor4", 		entityArmor ],
			[ "Studded Leather Armor", entityArmor ],
			[ "Armor5", 		entityArmor ],
			[ "Armor6", 		entityArmor ],
			[ "Leather Armor", 	entityArmor ],
			[ "Leather Jacket", 	entityArmor ],

			[ "Hawaiian Shirt",  	shirt],
			[ "Tee Shirt",  	shirt],

			[ "Mummy Wrapping",  	cloak ],
			[ "Elven Cloak",  	cloak ],
			[ "Leather Cloak",  	cloak ],
			[ "Hooded Cloak",  	cloak ],
			[ "Oilskin Cloak",  	cloak ],
			[ "Robe",  		cloak ],
			[ "Apron",  		cloak ],
			[ "Leather Cloak 2",  	cloak ],
			[ "Tattered Cloak",  	cloak ],
			[ "Opera Cloak",  	cloak ],
			[ "Ornamental Cope",  	cloak ],
			[ "Piece of Cloth",  	cloak ],

			[ "Low Boots", 		footwear ],
			[ "Dwarven Boots",  	footwear ],
			[ "High Boots",  	footwear ],
			[ "Combat Boots", 	footwear ],
			[ "Jungle Boots",  	footwear ],
			[ "Elven Boots",  	footwear ],
			[ "Mud Boots",  	footwear ],
			[ "Buckled Boots", 	footwear ],
			[ "Riding Boots", 	footwear ],
			[ "Snow Boots", 	footwear ],

			[ "Polished Shield", 	shield ],
			[ "Small Round Shield", shield ],
		];

		var entityDefnSetArmor = [];

		for (var i = 0; i < namesAndCategoriesOfArmor.length; i++)
		{
			var nameAndCategory = namesAndCategoriesOfArmor[i];
			var name = nameAndCategory[0];
			var appearance = name; // hack
			var category = nameAndCategory[1];

			var entityDefn = new EntityDefn
			(
				name, 
				ArrayHelper.concatenateArrays
				([
					categoriesCommon,
					[ "Armor", category.name ]
				]), 
				[
					CollidableDefn.Instances.Clear,
					new DrawableDefn(animation(images[name]).toRun(), sizeInPixels),
					new ItemDefn
					(
						appearance,
						1, // mass
						1, // stackSizeMax
						1, // relativeFrequency
						null, // initialize
						ItemDefn.UseEquip // use
					)
				]
			);

			entityDefnSetArmor.push(entityDefn);
		};

		entityDefnSets.push(entityDefnSetArmor);
		entityDefnSets["Group_Armor"] = entityDefnSetArmor;

		return new EntityDefnGroup("Armor", 1, entityDefnSets[0]);
	}

	DemoData.buildEntityDefns_Items_Tools = function
	(
		images, 
		animation,
		categories,
		categoriesCommon,
		sizeInPixels,
		itemPropertiesNoStack,
		itemPropertiesStandard,
		effectDoNothing,
		entityDefnSets
	)
	{		
		var entityDefnSet = [];

		var namesAndAppearances = 
		[
			[ "Key", ],
			[ "Lockpick"],
			[ "Credit Card" ],
			[ "Candle" ],
			[ "Candle2" ],
			[ "Lantern" ],
			[ "Oil Lamp" ],
			[ "Magic Lamp" ],
			[ "Expensive Camera" ],
			[ "Mirror" ],
			[ "Crystal Orb" ],
			[ "Eyeglasses" ],
			[ "Blindfold" ],
			[ "Towel" ],
			[ "Saddle" ],
			[ "Leash" ],
			[ "Stethoscope" ],
			[ "Tinning Kit" ],
			[ "Tin Opener" ],
			[ "Can of Grease" ],
			[ "Figurine" ],
			[ "Magic Marker" ],
			[ "Unarmed Land Mine" ],
			[ "Unarmed Bear Trap" ],
			[ "Tin Whistle" ],
			[ "Magic Whistle" ],
			[ "Flute" ],
			[ "Flute2" ],
			[ "Tooled Horn" ],
			[ "Horn of Cold" ],
			[ "Horn of Plenty" ],
			[ "Horn4" ],
			[ "Harp" ],
			[ "Harp2" ],
			[ "Bell" ],
			[ "Trumpet" ],
			[ "Drum" ],
			[ "Earthquake Drum" ],
			[ "Pickaxe" ],
			[ "Grappling Hook" ],
			[ "Unicorn Horn" ],
			[ "Candelabra" ],
			[ "Bell of Opening" ],
		];

		for (var i = 0; i < namesAndAppearances.length; i++)
		{
			var nameAndAppearance = namesAndAppearances[i];
			var name = nameAndAppearance[0];
			var appearance = nameAndAppearance[0]; // hack

			var entityDefn = new EntityDefn
			(
				name, 
				ArrayHelper.concatenateArrays
				([
					categoriesCommon,
					[ "Tool" ]
				]), 
				[
					CollidableDefn.Instances.Clear,
					new DrawableDefn(animation(images[name]).toRun(), sizeInPixels),
					new ItemDefn
					(
						appearance,
						1, // mass
						1, // stackSizeMax
						1, // relativeFrequency
						null, // initialize
						ItemDefn.UseEquip // use
					),

				]
			);

			entityDefnSet.push(entityDefn);
		};

		return new EntityDefnGroup("Tools", 1, entityDefnSet);
	}

	DemoData.buildEntityDefns_Items_Stones = function
	(
		images, 
		animation,
		categories,
		categoriesCommon,
		sizeInPixels,
		itemPropertiesNoStack,
		itemPropertiesStandard,
		effectDoNothing,
		entityDefnSets
	)
	{
		var namesOfStones = 
		[
			// precious stones

			"Dilithium Crystal", // 0
			"Diamond",
			"Ruby",
			"Jacinth",
			"Sapphire",
			"Black Opal", // 5
			"Emerald",
			"Turqoise",
			"Citrine",
			"Aquamarine",
			"Piece of Amber", // 10
			"Topaz",
			"Piece of Jet",
			"Opal",
			"Chrysoberyl",
			"Garnet", // 15
			"Amethyst",
			"Jasper",
			"Piece of Fluorite",
			"Piece of Jade",
			"Piece of Obsidian", // 20
			"Agate",

			// glass

			"White Glass",
			"Blue Glass",
			"Red Glass",
			"Yellowish Brown Glass", // 25
			"Orange Glass",
			"Yellow Glass",
			"Black Glass",
			"Green Glass",
			"Violet Glass", // 30

			// gray stones

			"Luckstone",
			"Loadstone",
			"Touchstone",
			"Flint",

			// rock

			"Rock", // 35
		];

		var appearancesOfStones = 
		[
			"White Gem","White Gem","Red Gem","Orange Gem",
			"Blue Gem","Black Gem","Green Gem","Green Gem",
			"Yellow Gem","Green Gem","Brownish Gem","Brownish Gem",
			"Black Gem","White Gem","Yellow Gem","Red Gem",
			"Violet Gem","Red Gem","Violet Gem","Black Gem",
			"Orange Gem","Green Gem","White Gem", "Blue Gem",
			"Red Gem","Brownish Gem","Orange Gem", "Yellow Gem",
			"Black Gem","Green Gem","Violet Gem","Gray Stone",
			"Gray Stone","Gray Stone","Gray Stone","Rock",
		];

		var entityDefnSetStones = [];

		for (var i = 0; i < namesOfStones.length; i++)
		{
			var appearance = appearancesOfStones[i];

			entityDefnSetStones.push
			(
				new EntityDefn
				(
					namesOfStones[i], 
					categoriesCommon, 
					[
						CollidableDefn.Instances.Clear,

						new DrawableDefn(animation(images[appearance]).toRun(), sizeInPixels),
						new ItemDefn
						(
							appearance,
							1, // mass
							1, // stackSizeMax
							1, // relativeFrequency
							ItemDefn.InitializeDoNothing,
							ItemDefn.UseDoNothing
						),
					]				
				)
			);
		}

		entityDefnSets.push(entityDefnSetStones);
		entityDefnSets["Group_Stones"] = entityDefnSetStones;

		var entityDefnSetValuables = [];

		entityDefnSetValuables.push
		(
			new EntityDefn
			(
				"Coins", 
				categoriesCommon, 
				ArrayHelper.concatenateArrays
				([
					itemPropertiesStandard,
					new DrawableDefn(animation(images["Coins"]).toRun(), sizeInPixels),
				])
			)

		);

		entityDefnSets["Group_Valuables"] = entityDefnSetValuables;
		entityDefnSets.push(entityDefnSetValuables);

		return new EntityDefnGroup("Stones", 1, entityDefnSets[0]);
		return new EntityDefnGroup("Valuables", 1, entityDefnSets[1]);
	}

	DemoData.buildEntityDefns_Items_EquipmentSocketDefnSet = function(categories)
	{
		var biped = new EquipmentSocketDefnSet
		(
			"Biped",
			[
				new EquipmentSocketDefn("Head", [ categories.Headwear.name ]),
				new EquipmentSocketDefn("Neck", [ categories.Neckwear.name ]),
				new EquipmentSocketDefn("Shirt", [ categories.Shirt.name ]),
				new EquipmentSocketDefn("Entity", [ categories.EntityArmor.name ]),
				new EquipmentSocketDefn("Cloak", [ categories.Cloak.name ]),
				new EquipmentSocketDefn("Hands", [ categories.Glove.name ] ),
				new EquipmentSocketDefn("Feet", [ categories.Footwear.name ] ),
				new EquipmentSocketDefn("Left Finger", [ categories.Ring.name ] ),
				new EquipmentSocketDefn("Right Finger", [ categories.Ring.name ] ),
				new EquipmentSocketDefn("Wielding", [ categories.Weapon.name ] ),
				new EquipmentSocketDefn("Ammunition", [ categories.Ammunition.name ] ),
			]
		);

		return biped;
	}

	DemoData.buildEntityDefnGroups_Movers = function(images, activityDefns, categories)
	{
		var returnValues = [];

		// convenience variables

		var animation = AnimationDefnSet.buildFromImage;

		var sizeInPixels = images["Floor"].sizeInPixels;

		var skillDefns = DemoData.buildSkillDefns();
		var spellDefns = DemoData.buildSpellDefns();
		var traitDefns = DemoData.buildTraitDefns();

		// player

		var equipmentSocketDefnSetBiped = DemoData.buildEntityDefns_Items_EquipmentSocketDefnSet
		(
			categories
		);

		var entityDefnCorpse = new EntityDefn
		(
			"Corpse",
			[
				"Collidable",
				"Drawable",
				"Item",
			],
			[
				CollidableDefn.Instances.Clear,
				new DrawableDefn(animation(images["Corpse"]).toRun(), sizeInPixels),
				new ItemDefn
				(
					"Corpse",
					1, // mass
					1, // stackSizeMax,
					1, // relativeFrequency
					ItemDefn.InitializeDevice,
					ItemDefn.UseDevice
				),
			]
		);

		returnValues.push(entityDefnCorpse);
		returnValues[entityDefnCorpse.name] = entityDefnCorpse;

		var moverDefnPlayer = new MoverDefn
		( 
			999, // difficulty
			1, // movesPerTurn 
			new MoverData_Demographics("Human", "Rogue", 1),
			new MoverData_Traits
			([
				new Trait(traitDefns["Strength"], 10),
				new Trait(traitDefns["Dexterity"], 10),
				new Trait(traitDefns["Willpower"], 10),
				new Trait(traitDefns["Constitution"], 10),
				new Trait(traitDefns["Charisma"], 10),
			]),
			new MoverData_Skills(skillDefns),
			new MoverData_Spells(spellDefns),
			new MoverDefn_Vitals(20, 1000),
			entityDefnCorpse,
			[] // attributeGroups
		);

		var visualForPlayerBase = images["Rogue"];

		var animationDefnSetPlayer = new AnimationDefnSet
		(
			"AnimationDefnSetPlayer",
			[
				new AnimationDefn
				(
					"AnimationDefnPlayer",
					"AnimationDefnPlayer", // animationDefnNameNext
					[
						new AnimationFrame
						(
							visualForPlayerBase,
							100 // ticksToHold
						),
						new AnimationFrame
						(
							images["Wizard"],
							100 // ticksToHold
						),

					]
				)
			]
		);

		var animationDefnSetReticle = new AnimationDefnSet
		(
			"AnimationDefnSetReticle",
			[
				new AnimationDefn
				(
					"AnimationDefnReticle",
					"AnimationDefnReticle", // animationDefnNameNext
					[
						new AnimationFrame
						(
							images["Reticle0"],
							10 // ticksToHold
						),
						new AnimationFrame
						(
							images["Reticle1"],
							10 // ticksToHold
						),
					]
				)
			]
		);

		var visualForPlayer = new VisualSet
		(
			"Player",
			[	
				animationDefnSetPlayer.toRun(),
				animationDefnSetReticle.toRun(),
			]
		);

		var drawableDefnPlayer = new DrawableDefn
		(

			visualForPlayer,
			sizeInPixels,
			1 // zIndex
		);

		var entityDefnPlayer = new EntityDefn
		(
			"Player", 	
			[
				"Actor",
				"Collidable", // formerly moved to end?
				"Container",
				"Drawable",
				"Equippable",
				"Killable",
				"Mover", 
				"Player",
			],
			// properties
			[
				new ActorDefn(activityDefns["Accept User Input"].name),
				CollidableDefn.Instances.Blocking,
				new EquippableDefn(equipmentSocketDefnSetBiped),
				new KillableDefn(16, null),
				moverDefnPlayer,
				drawableDefnPlayer,
			]
		);

		returnValues.push(entityDefnPlayer);
		returnValues["Player"] = entityDefnPlayer;

		// agents

		var categories = 
		[
			"Actor",
			"Collidable",
			"Container",
			"Drawable",
			"Enemy",
			"Equippable",
			"Killable", 
			"Mover", 
		];

		var agentDatas = DemoData.buildAgentDatas();

		for (var i = 0; i < agentDatas.length; i++)
		{
			var agentData = agentDatas[i];
			var agentName = agentData[0];
			var difficulty = agentData[1];

			var entityDefnForAgent = new EntityDefn
			(
				agentName, 	
				categories,
				// properties
				[
					new ActorDefn(activityDefns["Move Toward Player"].name),
					CollidableDefn.Instances.Blocking,
					new EquippableDefn(equipmentSocketDefnSetBiped),
					new KillableDefn(5, null),
					new MoverDefn
					(
						difficulty,
						1, // movesPerTurn 
						new MoverData_Demographics(null, null),
						new MoverData_Skills([]),
						new MoverData_Spells([]),
						new MoverData_Traits(10, 10, 10, 10, 10),
						new MoverDefn_Vitals(20, 1000),
						entityDefnCorpse,
						// attributeGroups
						[
							// todo
						]
					),

					new DrawableDefn
					(
						animation(images[agentName]).toRun(), 
						sizeInPixels,
						1 // zIndex
					),
				]
			);

			returnValues.push(entityDefnForAgent);
			returnValues[agentName] = entityDefnForAgent;
		}

		var returnValue = new EntityDefnGroup
		( 
			"Agents",
			0, // relativeFrequency
			returnValues
		);

		return returnValue;
	}

	DemoData.buildAgentDatas = function()
	{
		// resistances

		var acid = "acid";
		var aggravate = "aggravate";
		var cold = "cold";
		var disintegrate = disintegrate;
		var fire = "fire";
		var petrify = "petrify";
		var poison = "poison";
		var shock = "shock";
		var sleep = "sleep";
		var stun = "stun";
		var telepathy = "telepathy";

		// sizes

		var tiny = 0;
		var small = 1;
		var medium = 2;
		var large = 3;
		var huge = 4;

		// corpse drop frequency at http://www.steelypips.org/nethack/343/mon2-343.html

		var agentDatas = 
		[
			// name, difficulty, numberAppearing, attacks, baseLevel, baseExperience,
			// speed, base ac, base mr, alignment, frequency, genocidable,
			// weight, nutrition, size, resistances, resistancesConveyed

			// insects

			[ "Giant Ant", 		4, "1d3", [ "Bite:1d4" ], 		2, 20, 18, 3, 0, 	0, 3, true, 10, 10, 	tiny, null, 		.33, 	null ],
			[ "Killer Bee",		5, "2d6", [ "Sting:1d3" ],		1, 31, 18, -1, 0,	0, 2, true, 1, 5, 	tiny, [ poison ], 	.33, 	[ poison, 1 ] ],
			[ "Soldier Ant",	6, "1d3", [ "Bite:2d4", "Sting:3d4" ], 	3, 37, 18, 3, 0, 	0, 2, true, 20, 5, 	tiny, [ poison ], 	.33, 	[ poison, 1 ] ],
			[ "Fire Ant", 		6, "1d3", [ "Bite:2d4", "Sting:2d4" ],	3, 34, 18, 3, 10, 	0, 1, true, 30, 10, 	tiny, [ fire ], 	.25,	[ fire, 1 ] ],
			[ "Giant Beetle",	6, "1", [ "Bite:3d6" ],			5, 56, 6, 4, 0,		0, 3, true, 10, 10,	large, [ poison ], 	1, 	[ poison, 1 ], ],
			[ "Queen Bee",		12, "1", [ "Sting:1d8" ], 		9, 225, 24, -4, 0, 	0, 0, true, 1, 5, 	tiny, [ poison ], 	.25, 	[ poison, 1 ] ],

			// blobs

			[ "Acid Blob", 		2, "1", [ "Acid:1d8"  ],		1, 9, 3, 8, 0,		0, 2, true, 30, 10, 	tiny, [sleep, poison, acid, petrify], 			.33, 	null ],
			[ "Quivering Blob", 	6, "1", [ "Touch:1d8" ],		5, 59, 1, 8, 0, 	0, 2, true, 200, 100, 	small, [sleep, poison], 				.50, 	[ poison, 1 ] ],
			[ "Gelatinous Cube", 	8, "1", [ "Touch:2d4","Paralyze:1d4"],6, 76, 6, 8, 0, 	0, 2, true, 600, 150, 	large, [ fire, cold, shock, sleep, poison, acid, petrify], 	1, 	[ fire, 1, cold, 1, shock, 1, sleep, 1 ] ],

			// cockatrices

			[ "Chickatrice", 	7, "1d3", [ "Bite:1d2" ],		4, 136, 4, 8, 30,	0, 1, true, 10, 10, 	tiny, [ poison, petrify ], 	.25, [ poison, 1 ] ],
			[ "Cockatrice", 	7, "1", [ "Bite:1d3" ],			5, 149, 6, 6, 30,	0, 5, true, 30, 30, 	small, [ poison, petrify ], 	.50, [ poison, 1 ] ],
			[ "Pyrolisk", 		8, "1", [ "Gaze:2d6" ],			6, 82, 6, 6, 30,	0, 1, true, 30, 30,	small, [ fire, poison ], 	.33, [ fire, 1, poison, 1 ] ],

			// canines

			[ "Jackal", 		1, "1d3", [ "Bite:1d2" ],		0, 1, 12, 7, 0, 	0, 3, true, 300, 250, 	small, null, .50, null ],
			[ "Fox",		1, "1", [ "Bite:1d3" ],			0, 4, 15, 7, 0, 	0, 1, true, 300, 250, 	small, null, .33, null ],
			[ "Coyote", 		2, "1d3", [ "Bite:1d4" ],		1, 8, 12, 7, 0,		0, 1, true, 300, 250, 	small, null, .33, null ],
			[ "Werejackal" ],
			[ "Little Dog",		3, "1", [ "Bite:1d6" ],			3, 2, 20, 18, 6,	0, 1, true, 150, 150, 	small, null, .33, null ],
			[ "Dog",		5, "1", [ "Bite:1d6" ], 		4, 4, 44, 16, 0, 	0, 1, true, 400, 200, 	medium, null, .33, [ aggravate, 1 ] ],
			[ "Large Dog",		7, "1", [ "Bite:2d4" ],			6, 76, 15, 4, 0, 	0, 1, true, 800, 250, 	medium, null, .33, null ],
			[ "Dingo",		5, "1", [ "Bite:1d5" ],			4, 44, 16, 5, 0,	0, 1, true, 400, 200, 	medium, null, .33, null ],
			[ "Wolf",		6, "1d3", [ "Bite:2d4" ], 		6, 56, 12, 4, 0, 	0, 2, true, 500, 250, 	medium, null, .50, null ],
			[ "Werewolf" ],
			[ "Warg",		8, "1d3", [ "Bite:2d6" ],		7, 92, 12, 4, 0, 	-5, 2, true, 850, 350, 	medium, null, 		.50, null ],
			[ "Winter Wolf Cub", 	7, "1d3", [ "Bite:1d8", "Breath:1d8" ],	5, 64, 12, 4, 0, 	-5, 2, true, 250, 200, 	small, [ cold ], 	.50, [ cold, .33 ] 	],
			[ "Winter Wolf",	9, "1", [ "Bite:2d6", "Breath:2d6" ],	7, 102, 12, 4, 20, 	0, 1, true, 700, 300, 	large, [ cold ], 	1, [ cold, .47 ]	],
			[ "Hell Hound Pup",	9, "1d3", [ "Bite:2d6", "Breath:2d6" ],	9, 102, 12, 4, 20, 	-5, 1, true, 200, 200, 	small,	[ fire ], 	.33, [ fire, .47 ] ],
			[ "Hell Hound",		14, "1", [ "Bite:3d6", "Breath:3d6"],	12, 290, 14, 2, 20, 	0, 1, true, 600, 300, 	medium,	[ fire ], 	.33, [ fire, .80 ] ],
			[ "Cerberus" ],

			// eyes and spheres

			[ "Gas Spore", 		2, "1", [ "Explode:4d6" ],		2, 1, 12, 3, 10,  	0, 1, true, 10, 10, 	small, null, 0, null 		],
			[ "Floating Eye", 	3, "1", [ ], 				2, 17, 1, 9, 10, 	0, 5, true, 10, 10, 	small, null, .50, [ telepathy, 1] 	],
			[ "Flaming Sphere",	3, "1", [ "Explode:4d6" ],		6, 91, 13, 4, 0, 	0, 2, true, 10, 10, 	small, [ cold ], 0, null ],
			[ "Freezing Sphere",	3, "1", [ "Explode:4d6" ],		6, 91, 13, 4, 0, 	0, 2, true, 10, 10, 	small, [ fire ], 0, null ],
			[ "Shocking Sphere",	3, "1", [ "Explode:4d6" ],		6, 91, 13, 4, 0, 	0, 2, true, 10, 10, 	small, [ shock ], 0, null ],
			[ "Beholder" ],

			// felines

			[ "Kitten", 		3, "1", [ "Bite:1d6" ], 		2, 20, 18, 6, 0, 	0, 1, true, 150, 150, 	small, null, .33, null ],
			[ "Housecat",		5, "1", [ "Bite:1d6" ], 		4, 44, 16, 5, 0, 	0, 1, true, 200, 200, 	small, null, .33, null ],
			[ "Jaguar",		6, "1", [ "Claw:1d4", "Bite:1d8" ],	4, 44, 15, 6, 0, 	0, 2, true, 600, 300, 	large, null, 1, null ],
			[ "Lynx",		7, "1", [ "Claw:1d4", "Bite:1d10" ], 	5, 59, 15, 6, 0,	0, 1, true, 600, 300, 	large, null, .33, null ],
			[ "Panther", 		7, "1", [ "Claw:1d6", "Bite:1d10" ], 	5, 59, 15, 6, 0,	0, 1, true, 600, 300, 	large, null, 1, null ],
			[ "Large Cat",		7, "1", [ "Bite:2d4" ], 		6, 76, 15, 4, 0, 	0, 1, true, 250, 250, 	small, null, .33, null ],
			[ "Tiger",		8, "1", [ "Claw:2d4", "Bite:1d10" ], 	6, 73, 12, 6, 0,	0, 2, true, 600, 300, 	large, null, 1, null ],

			// gremlins and gargoyles

			[ "Gremlin" ], // .50
			[ "Gargoyle" ], // .50
			[ "Winged Gargoyle" ], // .33

			// humanoids

			[ "Hobbit", 		2, "1", [ "Weapon:1d6" ], 		1, 13, 9, 10, 0, 	6, 2, true, 500, 200, 	small, null, .50, null ],
			[ "Dwarf", 		4, "1", [ "Weapon:1d8" ],		2, 22, 6, 10, 10, 	4, 3, true, 900, 300, 	medium, null, .50, null ],
			[ "Bugbear" ], 		// 1.00
			[ "Dwarf Lord" ], 	// .50
			[ "Dwarf King" ], 	// .33
			[ "Mind Flayer" ],	// .33
			[ "Master Mind Flayer" ], // .33

			// minor demons

			[ "Manes", 		3, "2d6", [ "Claw:2x1d3", "Bite:1d4"], 1, 8, 3, 7, 0,		-7, 1, true, 100, 100, small, [ sleep, poison ], 0, null ],
			[ "Homunculus" ], // .33
			[ "Imp" ], // .25
			[ "Lemure" ], // 0
			[ "Quasit" ], // .50
			[ "Tengu" ], // .50

			// jellies

			[ "Blue Jelly" ], // .50
			[ "Spotted Jelly" ], // .33
			[ "Ochre Jelly" ], // .50

			// kobolds

			[ "Kobold", 		1, "1", [ "Weapon:1d4" ],		0, 6, 6, 10, 0, 	-2, 1, true, 400, 100, small, [ poison ], null ],
			[ "Large Kobold" ],
			[ "Kobold Lord" ],
			[ "Kobold Shaman" ],

			// leprechauns

			[ "Leprechaun" ],

			// mimics

			[ "Small Mimic", 	8, "1", [ "Claw:3d4" ],		7, 92, 3, 7, 0,		0, 2, true, 300, 200, medium, [ acid ], null ],
			[ "Large Mimic", 	9, "1", [ "Claw:3d4" ],		8, 113, 3, 7, 10,	0, 1, true, 600, 400, large, [ acid ], null ],
			[ "Giant Mimic", 	8, "1", [ "Claw:2x3d4" ],		9, 186, 3, 7, 20,	0, 1, true, 800, 500, large, [ acid ], null ],

			// nymphs

			[ "Wood Nymph", 	5, "1", [], 				3, 28, 12, 9, 20, 	0, 2, true, 600, 300, medium, null, null ],
			[ "Water Nymph", 	5, "1", [], 				3, 28, 12, 9, 20, 	0, 2, true, 600, 300, medium, null, null ],
			[ "Mountain Nymph", 	5, "1", [], 				3, 28, 12, 9, 20, 	0, 2, true, 600, 300, medium, null, null ],

			// orcs

			[ "Goblin", 		1, "1", [ "Weapon:1d4" ], 		0, 6, 9, 10, 0, 	-3, 2, true, 400, 100, small, null, null ],
			[ "Hobgoblin", 		3, "1", [ "Weapon:1d6" ],		1, 13, 9, 10, 0,	-4, 2, true, 1000, 200, medium, null, null ],
			[ "Orc", 		3, "2d6", [ "Weapon:1d8" ], 		1, 13, 9, 10, 0, 	-3, 0, true, 850, 150, medium, null, null ],
			[ "Hill Orc",		4, "2d6", [ "Weapon:1d6" ],		2, 22, 9, 10, 0,	-4, 2, true, 1000, 200, medium, null, null ],
			[ "Mordor Orc" ],
			[ "Uruk-hai" ],
			[ "Orc Shaman" ],
			[ "Orc Captain" ],

			// piercer

			[ "Rock Piercer",	4, "1", [ "Bite:2d6" ],		3, 28, 1, 3, 0,		0, 4, true, 200, 200, small, null, null ],
			[ "Iron Piercer",	6, "1", [ "Bite:3d6" ],		5, 63, 1, 0, 0,		0, 2, true, 400, 300, medium, null, null ],
			[ "Glass Piercer",	9, "1", [ "Bite:4d6" ],		7, 106, 1, 0, 0,	0, 4, true, 400, 300, medium, [ acid ], null ],

			// quadrupeds

			[ "Rothe", 		4, "1", [ "Claw:1d3", "Bite:1d8" ],	2, 17, 9, 7, 0, 	0, 4, true, 400, 100, large, null, null ],
			[ "Mumak" ],
			[ "Leocrotta" ],
			[ "Wumpus" ],
			[ "Titanothere" ],
			[ "Baluchitherium" ],
			[ "Mastodon" ],

			// rodents

			[ "Sewer Rat", 		1, "1d3", [ "Bite:1d3" ],		0, 1, 12, 7, 0,		0, 1, true, 20, 12, 	tiny, null, null ],
			[ "Giant Rat",		2, "1d3", [ "Bite:1d3" ],		1, 8, 10, 7, 0, 	0, 2, true, 30, 30, 	tiny, null, null ],
			[ "Rabid Rat",		4, "1", [ "Bite:2d4" ],		2, 17, 12, 6, 0, 	0, 1, true, 30, 5, 	tiny, [ poison ], null ],
			[ "Wererat" ],
			[ "Rock Mole" ],
			[ "Woodchuck" ],

			// spiders and centipedes

			[ "Cave Spider", 	3, "1d3", [ "Bite:1d2" ],		1, 8, 12, 3, 0, 	0, 2, true, 50, 50, 	tiny, [ poison ], [ poison, .07 ] ],
			[ "Centipede" ],
			[ "Giant Spider" ],
			[ "Scorpion" ],

			// trappers, lurkers above
			[ "Lurker Above" ],
			[ "Trapper" ],

			// horses and unicorns
			[ "White Unicorn" ],
			[ "Gray Unicorn" ],
			[ "Black Unicorn" ],
			[ "Pony" ],
			[ "Horse" ],
			[ "Warhorse" ],

			// vortices

			[ "Fog Cloud", 		4, "1", [ "Suffocate:1d6" ],		3, 38, 1, 0, 0, 	0, 2, true, 0, 0, 	huge, [ sleep, poison, petrify ], null ],
			[ "Dust Vortex" ],
			[ "Ice Vortex" ],
			[ "Energy Vortex" ],
			[ "Steam Vortex" ],
			[ "Fire Vortex" ],

			// worms
			[ "Baby Long Worm" ],
			[ "Baby Purple Worm" ],
			[ "Long Worm" ],
			[ "Purple Worm" ],

			// fantastical insects
			[ "Grid Bug", 		1, "1d3", [ "Bite:0d0"],		0, 1, 12, 9, 0, 	0, 3, true, 15, 10, 	tiny, [ shock, poison ], null ],
			[ "Xan" ],

			// lights
			[ "Yellow Light", 	5, "1", [ ],				3, 44, 15, 0, 0, 	0, 4, true, 0, 0, 	small, [ fire, cold, shock, disintegrate, sleep, poison, acid, petrify ], null ],
			[ "Black Light" ],

			// zruties

			[ "Zruty" ],

			// angelic beings

			[ "Couatl" ],
			[ "Aleax" ],
			[ "Angel" ],
			[ "Ki-rin" ],
			[ "Archon" ],

			// bats and birds

			[ "Bat", 		2, "1", [ "Bite:1d4" ], 		0, 6, 22, 8, 0,		0, 1, true, 20, 20, 	tiny, null, [  ] ],
			[ "Giant Bat", 		3, "1", [ "Bite:1d6" ],		2, 22, 22, 7, 0, 	0, 2, true, 30, 30, 	small, null, [  ] ],
			[ "Raven" ],
			[ "Vampire Bat" ],

			// centaurs

			[ "Plains Centaur" ],
			[ "Forest Centaur" ],
			[ "Mountain Centaur" ],

			// dragons

			[ "Baby Gray Dragon" ],
			[ "Baby Silver Dragon" ],
			[ "Baby Silver Dragon 2" ],
			[ "Baby Red Dragon" ],
			[ "Baby White Dragon" ],
			[ "Baby Orange Dragon" ],
			[ "Baby Black Dragon" ],
			[ "Baby Blue Dragon" ],
			[ "Baby Green Dragon" ],
			[ "Baby Yellow Dragon" ],		
			[ "Gray Dragon" ],
			[ "Silver Dragon" ],
			[ "Silver Dragon 2" ],
			[ "Red Dragon" ],
			[ "White Dragon" ],
			[ "Orange Dragon" ],
			[ "Black Dragon" ],
			[ "Blue Dragon" ],
			[ "Green Dragon" ],
			[ "Yellow Dragon" ],

			// elementals and stalkers

			[ "Stalker" ],
			[ "Air Elemental" ],
			[ "Fire Elemental" ],
			[ "Earth Elemental" ],
			[ "Water Elemental" ],

			// fungi and molds

			[ "Lichen", 		1, "1", [ "Touch:0d0" ],		0, 4, 1, 9, 0,		0, 4, true, 20, 200, 	small, null, null ],
			[ "Brown Mold" ],
			[ "Yellow Mold" ],
			[ "Green Mold" ],
			[ "Red Mold" ],
			[ "Shrieker" ],
			[ "Violet Fungus" ],

			// gnomes

			[ "Gnome", 		3, "1", [ "Weapon:1d6" ],		1, 13, 6, 10, 4,	0, 1, true, 650, 100, 	small, null, null ],
			[ "Gnome Lord", 	4, "1", [ "Weapon:1d8" ],		3, 33, 8, 10, 4,	0, 2, true, 700, 120, 	small, null, null ],
			[ "Gnomish Wizard",	5, "1", [ ],				3, 38, 10, 4, 10,	0, 1, true, 700, 120, 	small, null, null ],
			[ "Gnome King", 	6, "1", [ "Weapon:1d6" ],		5, 61, 10, 10, 20,	0, 1, true, 750, 150, 	small, null, null ],

			// large humanoids

			[ "Giant" ],
			[ "Stone Giant" ],
			[ "Hill Giant" ],
			[ "Fire Giant" ],
			[ "Frost Giant" ],
			[ "Storm Giant" ],
			[ "Ettin" ],
			[ "Titan" ],
			[ "Minotaur" ],

			// jabberwock

			[ "Jabberwock" ],
			[ "Jabberwock 2?" ],

			// keystone kops

			[ "Keystone Kop" ],
			[ "Kop Sergeant" ],
			[ "Kop Lieutenant" ],
			[ "Kop Kaptain" ],

			// liches

			[ "Lich" ],
			[ "Demilich" ],
			[ "Master Lich" ],
			[ "Arch-Lich" ],

			// mummies

			[ "Kobold Mummy", 	4, "1", [ "Claw:1d4" ],		3, 28, 8, 6, 20, 	-2, 1, true, 400, 50, 	small, [ cold, sleep, poison ], null ],
			[ "Gnome Mummy", 	5, "1", [ "Claw:1d6" ],		4, 41, 10, 6, 20, 	-3, 1, true, 650, 50, 	small, [ cold, sleep, poison ], null ],
			[ "Orc Mummy", 		6, "1", [ "Claw:1d6" ],		5, 56, 10, 5, 20, 	-4, 1, true, 850, 75, 	medium, [ cold, sleep, poison ], null ],
			[ "Dwarf Mummy", 	6, "1", [ "Claw:1d6" ],		5, 56, 10, 5, 20, 	-4, 1, true, 900, 150, 	medium, [ cold, sleep, poison ], null ],
			[ "Elf Mummy", 		7, "1", [ "Claw:2d4" ],		6, 73, 12, 4, 30, 	-5, 1, true, 800, 150, 	medium, [ cold, sleep, poison ], null ],
			[ "Elf Mummy", 		7, "1", [ "Claw:2x2d4" ],		6, 73, 12, 4, 30, 	-5, 1, true, 1450, 200, medium, [ cold, sleep, poison ], null ],
			[ "Ettin Mummy", 	8, "1", [ "Claw:2x2d6" ],		7, 92, 12, 4, 30, 	-6, 1, true, 1700, 250, huge, [ cold, sleep, poison ], null ],
			[ "Giant Mummy", 	10, "1", [ "Claw:2x3d4" ],		8, 116, 14, 3, 30, 	-7, 1, true, 2050, 375, huge, [ cold, sleep, poison ], null ],

			// nagas

			[ "Red Naga Spawn", 	4, "1", [ "Bite:1d4" ], 		3, 28, 10, 6, 0, 	0, 0, true, 500, 100, large, [ fire, poison ], [ fire, .1, poison, .1] ],
			[ "Black Naga Spawn", 	4, "1", [ "Bite:1d4" ], 		3, 28, 10, 6, 0, 	0, 0, true, 500, 100, large, [ acid, poison, petrify ], [ poison, .2] ],
			[ "Golden Naga Spawn", 	4, "1", [ "Bite:1d4" ], 		3, 28, 10, 6, 0, 	0, 0, true, 500, 100, large, [ poison ], [ poison, .2] ],
			[ "Golden Naga Spawn", 	4, "1", [ "Bite:1d4" ], 		3, 28, 10, 6, 0, 	0, 0, true, 500, 100, large, [ poison ], [ poison, .2] ],
			[ "Red Naga", 		8, "1", [ "Bite:2d4", "Breath:2d6"], 6, 82, 12, 4, 0,	-4, 1, true, 2600, 400, huge, [ fire, poison ], [ fire, .2, poison, .2 ] ],
			[ "Black Naga", 	10, "1", [ "Bite:2d6"], 		8, 132, 14, 2, 10, 	4, 1, true, 2600, 400, huge, [ poison, acid, petrify ], [ poison, .2 ]],
			[ "Golden Naga", 	13, "1", [ "Bite:2d6", "Magic:4d6"],	10, 239, 14, 2, 70, 	5, 1, true, 2600, 400, huge, [ poison ], [ poison, .2 ]],
			[ "Guardian Naga", 	16, "1", [ "Bite:1d6", "Spit:1d6", "Hug:2d4"],12, 295, 14, 2, 70, 	7, 1, true, 2600, 400, huge, [ poison ], [ poison, .2 ]],

			// ogres

			[ "Ogre" ],
			[ "Ogre Lord" ],
			[ "Ogre King" ],

			// puddings and amoeboids

			[ "Gray Ooze", 		4, "1", [ "Bite:2d8" ],		3, 28, 1, 8, 0,		0, 2, true, 500, 250, medium, [ fire, cold, poison, acid, petrify ], [ poison, .07, cold, .07, fire, .07 ] ],
			[ "Brown Pudding" ],
			[ "Black Pudding" ],
			[ "Green Slime" ],

			// quantum mechanic

			[ "Quantum Mechanic" ],

			// rust monster and disenchanter

			[ "Rust Monster" ],
			[ "Disenchanter" ],

			// snakes
			[ "Garter Snake", 	3, "1", [ "Bite:1d2" ], 		1, 8, 8, 8, 0, 		0, 1, true, 50, 60, 	tiny, null, null ],
			[ "Snake" ],
			[ "Water Moccasin" ],
			[ "Pit Viper" ],
			[ "Python" ],
			[ "Cobra" ],

			// trolls

			[ "Troll" ],
			[ "Ice Troll" ],
			[ "Rock Troll" ],
			[ "Water Troll" ],
			[ "Olog-Hai" ],

			// umber hulk

			[ "Umber Hulk" ],

			// vampires

			[ "Vampire" ],
			[ "Vampire Lord" ],
			[ "Vampire 2?" ],
			[ "Vlad the Impaler" ],

			// wraiths

			[ "Barrow Wight" ],
			[ "Wraith" ],
			[ "Nazgul" ],

			// xorn

			[ "Xorn" ],

			// apelike creatures

			[ "Monkey" ],
			[ "Ape" ],
			[ "Owlbear" ],
			[ "Yeti" ],
			[ "Carnivorous Ape" ],
			[ "Sasquatch" ],

			// zombies

			[ "Kobold Zombie" ],
			[ "Gnome Zombie" ],
			[ "Orc Zombie" ],
			[ "Dwarf Zombie" ],
			[ "Elf Zombie" ],
			[ "Human Zombie" ],
			[ "Ettin Zombie" ],
			[ "Giant Zombie" ],
			[ "Ghoul" ],
			[ "Skeleton" ],

			// golems

			[ "Straw Golem", 4, "1", [ "Claw:2x1d2" ], 		3, 28, 12, 10, 0, 	0, 1, false, 400, 0, large, [ sleep, poison ], null ],
			[ "Paper Golem" ],
			[ "Rope Golem" ],
			[ "Gold Golem" ],
			[ "Leather Golem" ],
			[ "Wood Golem" ],
			[ "Flesh Golem" ],
			[ "Clay Golem" ],
			[ "Stone Golem" ],
			[ "Glass Golem" ],
			[ "Iron Golem" ],

			// humans and elves

			[ "Human" ],
			[ "Wererat" ],
			[ "Werejackal" ],
			[ "Werewolf" ],
			[ "Elf" ],
			[ "Woodland-Elf" ],
			[ "Green-Elf" ],
			[ "Grey-Elf" ],
			[ "Elf-Lord" ],
			[ "Elvenking" ],
			[ "Doppelganger" ],
			[ "Nurse" ],
			[ "Shopkeeper" ],
			[ "Guard" ],
			[ "Prisoner" ],
			[ "Oracle" ],
			[ "Aligned Priest" ],
			[ "High Priest" ],
			[ "Soldier" ],
			[ "Sergeant" ],
			[ "Lieutenant" ],
			[ "Captain" ],
			[ "Watchman" ],
			[ "Watch Captain" ],
			[ "Medusa" ],
			[ "Wizard of Yendor" ],
			[ "Croesus" ],

			// ghosts and shades

			[ "Ghost" ],
			[ "Shade" ],

			// major demons

			[ "Water Demon" ],
			[ "Horned Devil" ],
			[ "Succubus" ],
			[ "Incubus" ],
			[ "Medusa" ],
			[ "Erinys" ],
			[ "Barbed Devil" ],
			[ "Marilith" ],
			[ "Vrock" ],
			[ "Hezrou" ],
			[ "Bone Devil" ],
			[ "Ice Devil" ],
			[ "Nalfeshnee" ],
			[ "Pit Fiend" ],
			[ "Balrog" ],
			[ "Jubilex" ],
			[ "Yeenoghu" ],
			[ "Orcus" ],
			[ "Geryon" ],
			[ "Dispater" ],
			[ "Baalzebub" ],
			[ "Asmodeus" ],
			[ "Demogorgon" ],
			[ "Death" ],
			[ "Pestilence" ],
			[ "Famine" ],
			[ "Mail Demon" ],
			[ "Djinni" ],
			[ "Sandestin" ],

			// sea monsters

			[ "Jellyfish" ],
			[ "Piranha" ],
			[ "Shark" ],
			[ "Giant Eel" ],
			[ "Electric Eel" ],
			[ "Kraken" ],

			// lizards

			[ "Newt", 		1, "1", [ "Bite:1d2" ], 	0, 1, 6, 8, 0, 		0, 5, true, 10, 20, tiny, null, null ],
			[ "Gecko", 		2, "1", [ "Bite:1d3" ], 	1, 8, 6, 8, 0, 		0, 5, true, 10, 20, tiny, null, null ],
			[ "Iguana", 		3, "1", [ "Bite:1d4" ], 	2, 17, 6, 7, 0, 	0, 5, true, 30, 30, tiny, null, null ],
			[ "Baby Crocodile" ],
			[ "Lizard" ],
			[ "Chameleon" ],
			[ "Crocodile" ],
			[ "Salamander" ],
			[ "Long Worm Entity" ],

			// player monsters

			[ "Archeologist" ],
			[ "Barbarian" ],
			[ "Caveman" ],
			[ "Cavewoman" ],
			[ "Healer" ],
			[ "Knight" ],
			[ "Monk" ],
			[ "Priest" ],
			[ "Priestess" ],
			[ "Ranger" ],
			[ "Rogue" ],
			[ "Samurai" ],
			[ "Tourist" ],
			[ "Valkyrie" ],
			[ "Wizard" ],

			// quest leaders

			[ "Unknown Quest Leader 1?" ],
			[ "Lord Carnarvon" ],
			[ "Pelias" ],
			[ "Shaman Karnov" ],
			[ "Unknown Quest Leader 2?" ],
			[ "Hippocrates" ],
			[ "King Arthur" ],
			[ "Grand Master" ],
			[ "Arch Priest" ],
			[ "Orion" ],
			[ "Master of Thieves" ],
			[ "Lord Sato" ],
			[ "Twoflower" ],
			[ "Norn" ],
			[ "Neferet the Green" ],

			// quest nemeses

			[ "Minion of Huhetotl" ],
			[ "Thoth Amon" ],
			[ "Chromatic Dragon" ],
			[ "Unknown Quest Nemesis 1?" ],
			[ "Cyclops" ],
			[ "Ixoth" ],
			[ "Master Kaen" ],
			[ "Nalzok" ],
			[ "Scorpius" ],
			[ "Master Assassin" ],
			[ "Ashikaga Takauji" ],
			[ "Lord Surtur" ],
			[ "The Dark One" ],

			// quest guardians

			[ "Student" ],
			[ "Chieftan" ],
			[ "Neanderthal" ],
			[ "Unknown Quest Guardian 1?" ],
			[ "Attendant" ],
			[ "Page" ],
			[ "Abbot" ],
			[ "Acolyte" ],
			[ "Hunter" ],
			[ "Thug" ],
			[ "Ninja" ],
			[ "Roshi" ],
			[ "Guide" ],
			[ "Warrior" ],
			[ "Apprentice" ],			
		];

		return agentDatas;
	}

	DemoData.buildRoles = function()
	{
		var skillDefns = DemoData.buildSkillDefns();

		var returnValues = 
		[
			new Role
			(
				"Wizard", 
				// ranks
				[
					new Role_Rank("Evoker", 0, null),
				],
				// skills
				[
					new Role_Skill(skillDefns["Attack Spells"], 4),
					new Role_Skill(skillDefns["Dagger"], 4),
					new Role_Skill(skillDefns["Quarterstaff"], 4),
				]
			),
		];

		return returnValues;
	}

	DemoData.buildSkillDefns = function()
	{
		var returnValues =
		[
			new SkillDefn("Attack Spells"),
			new SkillDefn("Dagger"),
			new SkillDefn("Quarterstaff"),
		];

		returnValues.addLookups("name");

		return returnValues;
	}

	DemoData.buildSpellDefns = function()
	{
		var returnValues =
		[
			// todo
		];

		returnValues.addLookups("name");

		return returnValues;
	}

	DemoData.buildTraitDefns = function()
	{
		var returnValues =
		[
			new TraitDefn("Strength"),
			new TraitDefn("Dexterity"),
			new TraitDefn("Willpower"),
			new TraitDefn("Constitution"),
			new TraitDefn("Charisma"),
		];

		returnValues.addLookups("name");

		return returnValues;
	}

	DemoData.buildMapTerrainsMines = function(imagesForTiles)
	{
		return DemoData.buildMapTerrainsDungeon(imagesForTiles);
	}

	DemoData.buildMapTerrainsDungeon = function(imagesForTiles)
	{
		this.Floor 		= new MapTerrain("Floor", 		".", 1, 	false, "#00aa00", imagesForTiles["Floor"]);
		this.Stone 		= new MapTerrain("Stone", 		"x", 1000000, 	true, "#000000", imagesForTiles["Stone"]);
		this.WallCornerNorth 	= new MapTerrain("WallCornerNorth", 	"+", 1000000, 	true, "#0000aa", imagesForTiles["WallDungeonCornerNorth"]);
		this.WallCornerSouth	= new MapTerrain("WallCornerSouth", 	"*", 1000000, 	true, "#0000aa", imagesForTiles["WallDungeonCornerSouth"]);
		this.WallEastWest 	= new MapTerrain("WallEastWest", 	"-", 1000000, 	true, "#0000aa", imagesForTiles["WallDungeonEastWest"]);
		this.WallNorthSouth 	= new MapTerrain("WallNorthSouth", 	"|", 1000000, 	true, "#0000aa", imagesForTiles["WallDungeonNorthSouth"]);

		var terrains = 
		[
			this.Stone,
			this.Floor,
			this.WallCornerNorth,
			this.WallCornerSouth,
			this.WallEastWest,
			this.WallNorthSouth,
		];

		terrains.addLookups("name");
		terrains.addLookups("codeChar");

		return terrains;
	}

	DemoData.buildMapTerrainsHades = function(imagesForTiles)
	{
		return DemoData.buildMapTerrainsDungeon(imagesForTiles);
	}

	DemoData.buildMapTerrainsLabyrinth = function(imagesForTiles)
	{
		return DemoData.buildMapTerrainsDungeon(imagesForTiles);
	}

	DemoData.buildMapTerrainsPuzzle = function(imagesForTiles)
	{
		return DemoData.buildMapTerrainsDungeon(imagesForTiles);
	}

	DemoData.buildMapTerrainsThrowback = function(imagesForTiles)
	{
		return DemoData.buildMapTerrainsDungeon(imagesForTiles);
	}

	DemoData.buildUniverseDefn = function(imagesForTiles, imagesForTilesTransparent)
	{	
		var imagesOpaque = DemoData.buildImageLookup(imagesForTiles);
		var imagesTransparent = DemoData.buildImageLookup(imagesForTilesTransparent);

		var actions = DemoData.buildActions();

		var activityDefns = DemoData.buildActivityDefns();

		var categories = DemoData.buildCategories();

		var entityDefnGroups = DemoData.buildEntityDefnGroups(imagesOpaque, activityDefns, categories);

		var venueDefns = DemoData.buildVenueDefns(imagesOpaque, actions, categories);

		var Branch = UniverseDefnVenueStructureBranch;

		var branchesMain = 
		[
			/*
			new Branch
			(
				"Tutorial", 
				"Tutorial", 
				false, 
				new Range(0, 0), 
				new Range(1, 1),
				[]
			),
			*/
			new Branch
			(
				"DungeonShallow", 
				"Dungeon",
				true, 
				new Range(0, 0), 
				new Range(5, 6),
				[
					new Branch
					(
						"MinesShallow",
						"Mines", 
						false,
						new Range(1, 4), 
						new Range(2, 4),
						[]
					),
					new Branch
					(
						"MinesTown", 
						"MinesTown",
						true,
						new Range(0, 0), 
						new Range(1, 1),
						[]
					),
					new Branch
					(
						"MinesDeep", 
						"Mines",
						true,
						new Range(0, 0), 
						new Range(2, 4),
						[]
					),
					new Branch
					(
						"MinesBottom", 
						"MinesBottom",
						true,
						new Range(0, 0), 
						new Range(1, 1),
						[]
					),
				]
			),
			new Branch
			(
				"Oracle",
				"Oracle", 
				true, 
				new Range(0, 0), 
				new Range(1, 1),
				[]			
			),
			new Branch
			(
				"DungeonDeep", 
				"Dungeon",
				true, 
				new Range(0, 0), 
				new Range(5, 6),
				[
					new Branch
					(
						"Puzzle", 
						"Puzzle",
						false,
						new Range(1, 4), 
						new Range(2, 4),
						[]
					),
				]
			),
			new Branch
			(
				"Labyrinth",
				"Labyrinth",
				true,
				new Range(0, 0),
				new Range(3, 5),
				[]
			),
			new Branch
			(
				"Island",
				"Island",
				true,
				new Range(0, 0),
				new Range(1, 1),
				[]				
			),
			new Branch
			(
				"Fortress",
				"Fortress",
				true,
				new Range(0, 0),
				new Range(1, 1),
				[]
			),
			new Branch
			(
				"Limbo",
				"Limbo",
				true,
				new Range(0, 0),
				new Range(1, 1),
				[]
			),
			new Branch
			(
				"Hades",
				"Hades",
				true,
				new Range(0, 0),
				new Range(10, 20),
				[]
			),
			new Branch
			(
				"Depths",
				"Dungeon", // todo
				true,
				new Range(0, 0),
				new Range(10, 20),
				[]
			),
		];

		var venueStructure = new UniverseDefnVenueStructure
		(
			new Branch
			(
				"Root",
				"Dungeon", // hack
				false,
				new Range(0, 0),
				new Range(0, 0),
				branchesMain
			)
		)

		var returnValue = new UniverseDefn
		(
			"UniverseDefn0",
			actions,
			activityDefns,
			categories,
			entityDefnGroups,
			venueDefns,
			venueStructure,
			// buildVenues
			function()
			{
				var returnValues = this.venueStructure.branchRoot.buildVenuesAndAddToList
				(
					this,
					[],
					0
				)

				return returnValues;
			}
		);

		return returnValue;
	}

	DemoData.buildVenueDefns = function(images, actions, categories)
	{
		var inputBindings = 
		[
			new InputBinding(InputKey.Instances.A, actions["Move West"].name),
			new InputBinding(InputKey.Instances.C, actions["Move Southeast"].name),
			new InputBinding(InputKey.Instances.D, actions["Move East"].name),
			new InputBinding(InputKey.Instances.E, actions["Move Northeast"].name),
			new InputBinding(InputKey.Instances.F, actions["Use Selected Item"].name),
			new InputBinding(InputKey.Instances.G, actions["Pick Up Item"].name),
			new InputBinding(InputKey.Instances.Q, actions["Move Northwest"].name),
			new InputBinding(InputKey.Instances.R, actions["Drop Selected Item"].name),
			new InputBinding(InputKey.Instances.T, actions["Target Selected Item"].name),
			new InputBinding(InputKey.Instances.U, actions["Use Emplacement"].name),
			new InputBinding(InputKey.Instances.W, actions["Move North"].name),
			new InputBinding(InputKey.Instances.X, actions["Move South"].name),
			new InputBinding(InputKey.Instances.Z, actions["Move Southwest"].name),

			new InputBinding(InputKey.Instances.BracketClose, actions["Select Next Item"].name),
			new InputBinding(InputKey.Instances.BracketOpen, actions["Select Previous Item"].name),
			new InputBinding(InputKey.Instances.Period, actions["Wait"].name),

		];

		var mapTerrainsDungeon = DemoData.buildMapTerrainsDungeon(images);

		var returnValues =
		[
			new VenueDefn
			(
				"Dungeon",
				categories,
				mapTerrainsDungeon,
				inputBindings,
				DemoData.venueGenerateDungeon
			),

			new VenueDefn
			(
				"Fortress",
				categories,
				mapTerrainsDungeon,
				inputBindings,
				DemoData.venueGenerateFortress
			),

			new VenueDefn
			(
				"Hades",
				categories,
				DemoData.buildMapTerrainsHades(images),
				inputBindings,
				DemoData.venueGenerateHades
			),

			new VenueDefn
			(
				"Mines",
				categories,
				DemoData.buildMapTerrainsMines(images),
				inputBindings,
				DemoData.venueGenerateMines
			),

			new VenueDefn
			(
				"MinesTown",
				categories,
				DemoData.buildMapTerrainsMines(images),
				inputBindings,
				DemoData.venueGenerateMines
			),

			new VenueDefn
			(
				"MinesBottom",
				categories,
				DemoData.buildMapTerrainsMines(images),
				inputBindings,
				DemoData.venueGenerateMines
			),

			new VenueDefn
			(
				"Island",
				categories,
				mapTerrainsDungeon,
				inputBindings,
				DemoData.venueGenerateIsland
			),

			new VenueDefn
			(
				"Labyrinth",
				categories,
				DemoData.buildMapTerrainsLabyrinth(images),
				inputBindings,
				DemoData.venueGenerateLabyrinth
			),

			new VenueDefn
			(
				"Limbo",
				categories,
				mapTerrainsDungeon,
				inputBindings,
				DemoData.venueGenerateLimbo
			),

			new VenueDefn
			(
				"Oracle",
				categories,
				mapTerrainsDungeon,
				inputBindings,
				DemoData.venueGenerateLimbo
			),

			new VenueDefn
			(
				"Puzzle",
				categories,
				DemoData.buildMapTerrainsPuzzle(images),
				inputBindings,
				DemoData.venueGeneratePuzzle
			),

			new VenueDefn
			(
				"SingleChamber",
				categories,
				mapTerrainsDungeon,
				inputBindings,
				DemoData.venueGenerateSingleChamber
			),

			new VenueDefn
			(
				"Throwback",
				categories,
				DemoData.buildMapTerrainsThrowback(images),
				inputBindings,
				DemoData.venueGenerateThrowback
			),

			new VenueDefn
			(
				"Tutorial",
				categories,
				mapTerrainsDungeon,
				inputBindings,
				DemoData.venueGenerateTutorial
			),
		];

		return returnValues;
	}

	DemoData.venueGenerateDungeon = function(universeDefn, venueDefn, venueIndex, venueDepth)
	{
		var entityDefnGroups = universeDefn.entityDefnGroups;
		var entityDefns = universeDefn.entityDefns;

		entityDefns.addLookups("name");

		var mapSizeInCells = new Coords(64, 64);
		var mapCellsAsStrings = [];
		var cellPos = new Coords(0, 0);
		var terrains = venueDefn.terrains;

		terrainCodeChar = terrains.Stone.codeChar;

		for (var y = 0; y < mapSizeInCells.y; y++)
		{
			var mapCellRowAsString = "";

			for (var x = 0; x < mapSizeInCells.x; x++)
			{
				mapCellRowAsString += terrainCodeChar;
			}

			mapCellsAsStrings.push(mapCellRowAsString);
		}

		var numberOfRooms = 12;

		var roomSizeMin = new Coords(4, 4);
		var roomSizeMax = new Coords(13, 13);
		var roomSizeRange = roomSizeMax.clone().subtract(roomSizeMin);

		terrainCodeChar = terrains.Floor.codeChar;

		var roomBoundsSetSoFar = [];

		while (roomBoundsSetSoFar.length < numberOfRooms)
		{
			var doesRoomOverlapAnother = true;

			while (doesRoomOverlapAnother == true)
			{	
				var roomSize = roomSizeRange.clone().random().add
				(
					roomSizeMin
				);

				var roomSizePlusOnes = roomSize.clone().add
				(
					Coords.Instances.Ones
				);

				var roomPosRange = mapSizeInCells.clone().subtract
				(
					roomSize
				).subtract
				(
					Coords.Instances.Twos
				);

				var roomPos = roomPosRange.clone().random().add
				(
					Coords.Instances.Ones
				);

				doesRoomOverlapAnother = Bounds.doBoundsInSetsOverlap
				(
					[ new Bounds(roomPos, roomSizePlusOnes) ],
					roomBoundsSetSoFar
				);
			}

			var roomBounds = new Bounds(roomPos, roomSize);

			roomBoundsSetSoFar.push(roomBounds);
		}

		var rooms = [];

		for (var r = 0; r < numberOfRooms; r++)
		{
			var roomBounds = roomBoundsSetSoFar[r];
			var room = new RoomData(roomBounds.pos, roomBounds.size);
			rooms.push(room);
		}

		for (var r = 0; r < numberOfRooms; r++)
		{
			var room = rooms[r];
			var roomPos = room.bounds.pos;
			var roomSize = room.bounds.size;
			var roomMax = roomPos.clone().add(room.bounds.size);

			for (var y = roomPos.y; y < roomMax.y; y++)
			{	
				var mapCellRowAsString = mapCellsAsStrings[y];

				for (var x = roomPos.x; x < roomMax.x; x++)
				{	
					if (x == roomPos.x || x == roomMax.x - 1)
					{
						if (y == roomPos.y)
						{
							terrainCodeChar = terrains.WallCornerNorth.codeChar;	
						}
						else if (y == roomMax.y - 1)
						{
							terrainCodeChar = terrains.WallCornerSouth.codeChar;	
						}
						else
						{
							terrainCodeChar = terrains.WallNorthSouth.codeChar;
						}
					}
					else if (y == roomPos.y || y == roomMax.y - 1)
					{
						terrainCodeChar = terrains.WallEastWest.codeChar;
					}
					else
					{
						terrainCodeChar = terrains.Floor.codeChar;
					}

					mapCellRowAsString = 
						mapCellRowAsString.substring(0, x)
						+ terrainCodeChar
						+ mapCellRowAsString.substring(x + 1);
				}

				mapCellsAsStrings[y] = mapCellRowAsString;
			}
		}

		var roomsConnected = [ rooms[0] ];
		var roomsToConnect = [];

		for (var r = 1; r < numberOfRooms; r++)
		{
			roomsToConnect.push(rooms[r]);
		}

		var doorwayPositions = [];

		while (roomsToConnect.length > 0)
		{
			var nearestRoomsSoFar = null;
			var distanceBetweenNearestRoomsSoFar = null;

			for (var r = 0; r < roomsConnected.length; r++)
			{
				var roomConnected = roomsConnected[r];
				var roomConnectedCenter = roomConnected.bounds.center;

				for (var s = 0; s < roomsToConnect.length; s++)
				{
					var roomToConnect = roomsToConnect[s];
					var roomToConnectCenter = roomToConnect.bounds.center;

					var distance = roomToConnectCenter.clone().subtract
					(
						roomConnectedCenter
					).absolute().sumOfXAndY();

					if 
					(
						nearestRoomsSoFar == null
						|| distance < distanceBetweenNearestRoomsSoFar
					)
					{
						nearestRoomsSoFar = 
						[
							roomConnected,
							roomToConnect,
						];

						distanceBetweenNearestRoomsSoFar = distance;
					}	
				}
			}

			var roomConnected = nearestRoomsSoFar[0];
			var roomToConnect = nearestRoomsSoFar[1];

			var roomConnectedBounds = roomConnected.bounds;
			var roomToConnectBounds = roomToConnect.bounds;

			var fromPos = roomConnectedBounds.pos.clone().add
			(
				roomConnectedBounds.size.clone().subtract
				(
					Coords.Instances.Twos
				).random()
			).add
			(
				Coords.Instances.Ones
			);

			var toPos = roomToConnectBounds.pos.clone().add
			(
				roomToConnectBounds.size.clone().subtract
				(
					Coords.Instances.Twos
				).random()
			).add
			(
				Coords.Instances.Ones
			);

			var displacementToRoomToConnect = toPos.clone().subtract
			(
				fromPos
			);

			var directionToRoomToConnect = displacementToRoomToConnect.clone();

			var dimensionIndexToClear = directionToRoomToConnect.dimensionIndexOfSmallest(0);

			if (Bounds.doBoundsOverlapInDimension(roomConnectedBounds, roomToConnectBounds, 0) == true)
			{
				dimensionIndexToClear = 0;
			}
			else if (Bounds.doBoundsOverlapInDimension(roomConnectedBounds, roomToConnectBounds, 1) == true)
			{
				dimensionIndexToClear = 1;
			}

			directionToRoomToConnect.dimension_Set
			(
				dimensionIndexToClear,
				0 // valueToSet
			).directions();

			if (directionToRoomToConnect.x > 0)
			{	
				fromPos.x = roomConnectedBounds.max.x;
				toPos.x = roomToConnectBounds.pos.x - 1;
			}
			else if (directionToRoomToConnect.x < 0)
			{
				fromPos.x = roomConnectedBounds.pos.x - 1;
				toPos.x = roomToConnectBounds.max.x;
			}
			else if (directionToRoomToConnect.y > 0)
			{
				fromPos.y = roomConnectedBounds.max.y;
				toPos.y = roomToConnectBounds.pos.y - 1;
			}
			else if (directionToRoomToConnect.y < 0)
			{
				fromPos.y = roomConnectedBounds.pos.y - 1;
				toPos.y = roomToConnectBounds.max.y;
			}

			doorwayPositions.push(fromPos.clone().subtract(directionToRoomToConnect));
			doorwayPositions.push(toPos.clone().add(directionToRoomToConnect));

			var cellPos = fromPos.clone();

			terrainCodeChar = terrains.Floor.codeChar;

			var mapCellRowAsString = mapCellsAsStrings[cellPos.y];

			var terrainCodeCharsForWalls = 
				terrains.WallEastWest.codeChar +
				terrains.WallNorthSouth.codeChar;

			while (displacementToRoomToConnect.equals(Coords.Instances.Zeroes) == false)
			{
				var mapCellRowAsString = mapCellsAsStrings[cellPos.y];

				var terrainCodeCharExisting = mapCellRowAsString[cellPos.x];

				mapCellRowAsString = 
					mapCellRowAsString.substring(0, cellPos.x)
					+ terrainCodeChar
					+ mapCellRowAsString.substring(cellPos.x + 1); 

				mapCellsAsStrings[cellPos.y] = mapCellRowAsString;

				displacementToRoomToConnect.overwriteWith
				(
					toPos
				).subtract
				(
					cellPos
				);

				directionToRoomToConnect.overwriteWith
				(
					displacementToRoomToConnect
				).dimension_Set
				(
					directionToRoomToConnect.dimensionIndexOfSmallest(0),
					0 // valueToSet
				).directions();

				cellPos.add(directionToRoomToConnect);
			}

			roomsToConnect.splice(roomsToConnect.indexOf(roomToConnect), 1);
			roomsConnected.push(roomToConnect);			
		}

		for (var i = 0; i < doorwayPositions.length; i++)
		{
			var cellPos = doorwayPositions[i];

			var mapCellRowAsString = mapCellsAsStrings[cellPos.y];

			mapCellRowAsString = 
				mapCellRowAsString.substring(0, cellPos.x)
				+ terrainCodeChar
				+ mapCellRowAsString.substring(cellPos.x + 1); 

			mapCellsAsStrings[cellPos.y] = mapCellRowAsString;
		}

		var entities =
		[
			// stairs
			new Entity
			(
				"StairsUp", 
				entityDefns["StairsUp"].name, 
				rooms[0].bounds.center.clone().floor(), 
				// propertyValues
				[
					new PortalData
					(
						"Venue" + (venueIndex - 1),
						"StairsDown"
					),
				]
			),

			new Entity
			(
				"StairsDown", 
				entityDefns["StairsDown"].name, 
				rooms[1].bounds.center.clone().floor(), 
				// propertyValues
				[
					new PortalData
					(
						"Venue" + (venueIndex + 1),
						"StairsUp"
					),
				]
			),

			// movers
			// new Entity("Enemy 0", entityDefns["Giant Ant"], rooms[1].bounds.center.clone().floor()),
			Entity.fromDefn
			(
				"Mover Generator",
				MoverGenerator.EntityDefn,
				null // pos
			),
		];

		for (var i = 0; i < doorwayPositions.length; i++)
		{
			var entityForDoor = new Entity
			(
				"Door" + i,
				entityDefns["Door"].name,
				doorwayPositions[i]
			);

			entities.push(entityForDoor);
		}

		var chancesForItemPerRoom = 2;
		var probabilityOfItemPerChance = 1;

		var entityDefnGroupsForItems = 
		[
			entityDefnGroups["Armor"],
			entityDefnGroups["Food"],
			entityDefnGroups["Potions"],
			entityDefnGroups["Scrolls"],
			entityDefnGroups["Spellbooks"],
			entityDefnGroups["Stones"],
			entityDefnGroups["Tools"],
			entityDefnGroups["Wands"],
			entityDefnGroups["Weapons"],
		];

		var sumOfFrequenciesForAllGroups = 0;

		for (var g = 0; g < entityDefnGroupsForItems.length; g++)
		{
			var entityDefnGroup = entityDefnGroupsForItems[g];
			sumOfFrequenciesForAllGroups += entityDefnGroup.relativeFrequency;
		}

		for (var r = 0; r < numberOfRooms; r++)
		{
			var room = rooms[r];

			for (var c = 0; c < chancesForItemPerRoom; c++)
			{
				var randomValue = Globals.Instance.randomizer.getNextRandom();

				if (randomValue <= probabilityOfItemPerChance)
				{	
					randomValue = 
						Globals.Instance.randomizer.getNextRandom() 
						* sumOfFrequenciesForAllGroups;

					var sumOfFrequenciesForGroupsSoFar = 0;

					var entityDefnGroupIndex = 0;

					for (var g = 0; g < entityDefnGroupsForItems.length; g++)
					{
						var entityDefnGroup = entityDefnGroupsForItems[g];
						sumOfFrequenciesForGroupsSoFar += entityDefnGroup.relativeFrequency;

						if (sumOfFrequenciesForGroupsSoFar >= randomValue)
						{
							entityDefnGroupIndex = g;
							break;
						}
					}

					var entityDefnGroup = entityDefnGroupsForItems[entityDefnGroupIndex];
					var entityDefns = entityDefnGroup.entityDefns;

					var entityDefnIndex = Math.floor
					(
						Globals.Instance.randomizer.getNextRandom() 
						* entityDefns.length
					);

					var entityDefnForItem = entityDefns
					[
						entityDefnIndex
					];

					var pos = room.bounds.size.clone().subtract
					(
						Coords.Instances.Twos
					).random().add
					(
						room.bounds.pos
					).add
					(
						Coords.Instances.Ones
					)

					var entityForItem = new Entity
					(
						entityDefnForItem.name, 
						entityDefnForItem.name, 
						pos 
					);

					entities.push(entityForItem);
				}
			}
		}

		var map = new Map
		(
			"Venue" + venueIndex + "Map",
			venueDefn.terrains,
			new Coords(16, 16), // hack - cellSizeInPixels
			mapCellsAsStrings
		);

		var returnValue = new Venue
		(
			"Venue" + venueIndex,
			venueDepth,
			venueDefn,
			new Coords(480, 480), // sizeInPixels
			map,
			entities
		);

		return returnValue;
	}

	DemoData.venueGenerateFortress = function(universe, venueDefn, venueIndex, venueDepth)
	{
		return DemoData.venueGenerateDungeon(universe, venueDefn, venueIndex, venueDepth);
	}

	DemoData.venueGenerateHades = function(universe, venueDefn, entityDefns, venueIndex, venueDepth)
	{
		return DemoData.venueGenerateDungeon(universe, venueDefn, venueIndex, venueDepth);
	}

	DemoData.venueGenerateIsland = function(universe, venueDefn, venueIndex, venueDepth)
	{
		return DemoData.venueGenerateDungeon(universe, venueDefn, venueIndex, venueDepth);
	}

	DemoData.venueGenerateMines = function(universe, venueDefn, venueIndex, venueDepth)
	{
		return DemoData.venueGenerateDungeon(universe, venueDefn, venueIndex, venueDepth);
	}

	DemoData.venueGenerateOracle = function(universe, venueDefn, venueIndex, venueDepth)
	{
		return DemoData.venueGenerateDungeon(universe, venueDefn, venueIndex, venueDepth);
	}

	DemoData.venueGenerateLabyrinth = function(universe, venueDefn, venueIndex, venueDepth)
	{
		return DemoData.venueGenerateDungeon(universe, venueDefn, venueIndex, venueDepth);
	}

	DemoData.venueGenerateLimbo = function(universe, venueDefn, venueIndex, venueDepth)
	{
		return DemoData.venueGenerateDungeon(universe, venueDefn, venueIndex, venueDepth);
	}

	DemoData.venueGeneratePuzzle = function(universe, venueDefn, venueIndex, venueDepth)
	{
		return DemoData.venueGenerateDungeon(universe, venueDefn, venueIndex, venueDepth);
	}

	DemoData.venueGenerateSingleChamber = function(universe, venueDefn, venueIndex, venueDepth)
	{
		return DemoData.venueGenerateDungeon(universe, venueDefn, venueIndex, venueDepth);
	}

	DemoData.venueGenerateThrowback = function(universe, venueDefn, venueIndex, venueDepth)
	{
		return DemoData.venueGenerateDungeon(universe, venueDefn, venueIndex, venueDepth);
	}

	DemoData.venueGenerateTutorial = function(universeDefn, venueDefn, venueIndex, venueDepth)
	{
		var sizeInCells = new Coords(16, 16);

		var stringForTopAndBottomRows = "x";
		var stringForOtherRows = "x";

		for (var x = 1; x < sizeInCells.x - 1; x++)
		{
			stringForTopAndBottomRows += "x";
			stringForOtherRows += ".";
		}		 

		stringForTopAndBottomRows += "x";
		stringForOtherRows += "x";

		var mapCellsAsStrings = [];

		mapCellsAsStrings.push(stringForTopAndBottomRows);

		for (var y = 1; y < sizeInCells.y - 1; y++)
		{
			mapCellsAsStrings.push(stringForOtherRows);
		}

		mapCellsAsStrings.push(stringForTopAndBottomRows);

		var map = new Map
		(
			"Venue" + venueIndex + "Map",
			venueDefn.terrains,
			new Coords(16, 16), // hack - cellSizeInPixels
			mapCellsAsStrings
		);

		var entityDefns = universeDefn.entityDefns;

		var entities =
		[
			// stairs

			new Entity
			(
				"StairsDown", 
				entityDefns["StairsDown"].name, 
				sizeInCells.clone().subtract(Coords.Instances.Ones),
				// propertyValues
				[
					new PortalData
					(
						"Venue" + (venueIndex + 1),
						"StairsUp"
					),
				]
			),

		];

		var returnValue = new Venue
		(
			"Venue" + venueIndex,
			venueDepth,
			venueDefn,
			new Coords(480, 480), // sizeInPixels
			map,
			entities
		);

		var locationForMessages = new Location(returnValue.name, new Coords(0, 0));

		//Font.spawnMessageFixed("Tutorial", locationForMessages);

		return returnValue;
	}

	DemoData.buildImageLookup = function(imagesForTiles)
	{
		var returnValue = [];

		var tileNamesAndPositions = 
		[
			// terrains
			[ "Floor", new Coords(8, 21) ],
			[ "Stone", new Coords(29, 20) ],

			[ "WallCaveCornerNorth", new Coords(15, 25) ],
			[ "WallCaveCornerSouth", new Coords(17, 25) ],
			[ "WallCaveEastWest", new Coords(14, 25) ],	
			[ "WallCaveNorthSouth", new Coords(13, 25) ],	

			[ "WallDungeonCornerNorth", new Coords(32, 20) ],
			[ "WallDungeonCornerSouth", new Coords(34, 20) ],
			[ "WallDungeonEastWest", new Coords(31, 20) ],	
			[ "WallDungeonNorthSouth", new Coords(30, 20) ],

			[ "WallHadesCornerNorth", new Coords(26, 25) ],
			[ "WallHadesCornerSouth", new Coords(28, 25) ],
			[ "WallHadesEastWest", new Coords(25, 25) ],	
			[ "WallHadesNorthSouth", new Coords(24, 25) ],

			[ "WallPuzzleCornerNorth", new Coords(8, 26) ],
			[ "WallPuzzleCornerSouth", new Coords(10, 26) ],
			[ "WallPuzzleEastWest", new Coords(7, 26) ],	
			[ "WallPuzzleNorthSouth", new Coords(6, 26) ],

			// emplacements

			[ "Blood", new Coords(3, 4) ],
			[ "Door", new Coords(4, 21) ],
			[ "Gravestone", new Coords(16, 21) ],
			[ "StairsUp", new Coords(11, 21) ],
			[ "StairsDown", new Coords(12, 21) ],

			// items - unsorted

			[ "Chest", new Coords(34, 9) ],
			[ "Coins", new Coords(29, 19) ],
			[ "Corpse", new Coords(36, 15) ],

			// items - foods

			[ "Eucalyptus Leaf", new Coords(3, 16) ],
			[ "Apple", new Coords(4, 16) ],
			[ "Orange", new Coords(5, 16) ],
			[ "Pear", new Coords(6, 16) ],
			[ "Melon", new Coords(7, 16) ],
			[ "Banana", new Coords(8, 16) ],
			[ "Carrot", new Coords(9, 16) ],
			[ "Sprig of Wolfsbane", new Coords(10, 16) ],
			[ "Garlic Clove", new Coords(11, 16) ],
			[ "Slime Mold", new Coords(12, 16) ],
			[ "Royal Jelly", new Coords(13, 16) ],
			[ "Cream Pie", new Coords(14, 16) ],
			[ "Candy Bar", new Coords(15, 16) ],
			[ "Fortune Cookie", new Coords(16, 16) ],
			[ "Pancake", new Coords(17, 16) ],
			[ "Lembas Wafer", new Coords(18, 16) ],
			[ "Cram Ration", new Coords(19, 16) ],
			[ "Food Ration", new Coords(20, 16) ],
			[ "K Ration", new Coords(21, 16) ],
			[ "C Ration", new Coords(22, 16) ],
			[ "Tin", new Coords(23, 16) ],

			// items - potions

			[ "Ruby Potion", new Coords(24, 16) ],
			[ "Pink Potion", new Coords(25, 16) ],
			[ "Orange Potion", new Coords(26, 16) ],
			[ "Yellow Potion", new Coords(27, 16) ],
			[ "Emerald Potion", new Coords(28, 16) ],
			[ "Dark Green Potion", new Coords(29, 16) ],
			[ "Sky Blue Potion", new Coords(30, 16) ],
			[ "Cyan Potion", new Coords(31, 16) ],
			[ "Brilliant Blue Potion", new Coords(32, 16) ],
			[ "Magenta Potion", new Coords(33, 16) ],
			[ "Purple-Red Potion", new Coords(34, 16) ],
			[ "Puce Potion", new Coords(35, 16) ],
			[ "Milky Potion", new Coords(36, 16) ],
			[ "Swirly Potion", new Coords(37, 16) ],
			[ "Bubbly Potion", new Coords(38, 16) ],
			[ "Smoky Potion", new Coords(39, 16) ],
			[ "Cloudy Potion", new Coords(39, 16) ],
			[ "Effervescent Potion", new Coords(0, 17) ],
			[ "Black Potion", new Coords(1, 17) ],
			[ "Golden Potion", new Coords(2, 17) ],
			[ "Brown Potion", new Coords(3, 17) ],
			[ "Fizzy Potion", new Coords(4, 17) ],
			[ "Dark Potion", new Coords(5, 17) ],
			[ "White Potion", new Coords(6, 17) ],
			[ "Murky Potion", new Coords(7, 17) ],
			[ "Clear Potion", new Coords(8, 17) ],

			// items - armor - helmets

			[ "Elven Leather Helmet", new Coords(25, 11) ],
			[ "Orcish Helmet", new Coords(26, 11) ],
			[ "Dwarvish Helmet", new Coords(27, 11) ],
			[ "Black Hat", new Coords(28, 11) ],
			[ "Cornuthaum", new Coords(29, 11) ],
			[ "Dunce Cap", new Coords(30, 11) ],
			[ "Cooking Pot", new Coords(31, 11) ],
			[ "Plumed Helmet", new Coords(32, 11) ],
			[ "Etched Helmet", new Coords(33, 11) ],
			[ "Crested Helmet", new Coords(34, 11) ],
			[ "Visored Helmet", new Coords(35, 11) ],

			// items - armor - entity armor

			[ "Gray Dragonscale Mail", new Coords(36, 11) ],
			[ "Silver Dragonscale Mail", new Coords(37, 11) ],
			[ "Rainbow Dragonscale Mail", new Coords(38, 11) ],
			[ "Red Dragonscale Mail", new Coords(39, 11) ],
			[ "White Dragonscale Mail", new Coords(0, 12) ],
			[ "Orange Dragonscale Mail", new Coords(1, 12) ],
			[ "Black Dragonscale Mail", new Coords(2, 12) ],
			[ "Blue Dragonscale Mail", new Coords(3, 12) ],
			[ "Green Dragonscale Mail", new Coords(4, 12) ],
			[ "Yellow Dragonscale Mail", new Coords(5, 12) ],

			[ "Gray Dragon Scales", new Coords(6, 12) ],
			[ "Silver Dragon Scales", new Coords(7, 12) ],
			[ "Rainbow Dragon Scales", new Coords(8, 12) ],
			[ "Red Dragon Scales", new Coords(9, 12) ],
			[ "White Dragon Scales", new Coords(10, 12) ],
			[ "Orange Dragon Scales", new Coords(11, 12) ],
			[ "Black Dragon Scales", new Coords(12, 12) ],
			[ "Blue Dragon Scales", new Coords(13, 12) ],
			[ "Green Dragon Scales", new Coords(14, 12) ],
			[ "Yellow Dragon Scales", new Coords(15, 12) ],

			[ "Plate Mail", new Coords(16, 12) ],
			[ "Crystal Plate Mail", new Coords(17, 12) ],
			[ "Bronze Plate Mail", new Coords(18, 12) ],
			[ "Armor1", new Coords(19, 12) ],
			[ "Armor2", new Coords(20, 12) ],
			[ "Elven Mithril Shirt", new Coords(21, 12) ],
			[ "Dwarven Mithril Shirt", new Coords(22, 12) ],
			[ "Armor3", new Coords(23, 12) ],
			[ "Orcish Chain Mail", new Coords(24, 12) ],
			[ "Armor4", new Coords(25, 12) ],
			[ "Studded Leather Armor", new Coords(26, 12) ],
			[ "Armor5", new Coords(27, 12) ],
			[ "Armor6", new Coords(28, 12) ],
			[ "Leather Armor", new Coords(29, 12) ],

			[ "Leather Jacket", new Coords(30, 12) ],
			[ "Hawaiian Shirt", new Coords(31, 12) ],
			[ "Tee Shirt", new Coords(32, 12) ],
			[ "Mummy Wrapping", new Coords(33, 12) ],

			[ "Elven Cloak", new Coords(34, 12) ],
			[ "Leather Cloak", new Coords(35, 12) ],
			[ "Hooded Cloak", new Coords(36, 12) ],
			[ "Oilskin Cloak", new Coords(37, 12) ],
			[ "Robe", new Coords(38, 12) ],
			[ "Apron", new Coords(39, 12) ],
			[ "Leather Cloak 2", new Coords(0, 13) ],
			[ "Tattered Cloak", new Coords(1, 13) ],
			[ "Opera Cloak", new Coords(2, 13) ],
			[ "Ornamental Cope", new Coords(3, 13) ],
			[ "Piece of Cloth", new Coords(4, 13) ],

			// items - armor - shields

			[ "ShieldSmall", new Coords(5, 13) ],
			[ "ShieldGreen", new Coords(6, 13) ],
			[ "ShieldWhiteHanded", new Coords(7, 13) ],
			[ "ShieldRedEyed", new Coords(8, 13) ],
			[ "ShieldLarge", new Coords(9, 13) ],
			[ "Small Round Shield", new Coords(10, 13) ],
			[ "Polished Shield", new Coords(11, 13) ],

			// items - armor - gloves

			[ "Padded Gloves", new Coords(12, 13) ],
			[ "Old Gloves", new Coords(13, 13) ],
			[ "Riding Gloves", new Coords(14, 13) ],
			[ "Snow Gloves", new Coords(15, 13) ],

			// items - armor - boots

			[ "Low Boots", new Coords(16, 13) ],
			[ "Dwarven Boots", new Coords(17, 13) ],
			[ "High Boots", new Coords(18, 13) ],
			[ "Combat Boots", new Coords(19, 13) ],
			[ "Jungle Boots", new Coords(20, 13) ],
			[ "Elven Boots", new Coords(21, 13) ],
			[ "Mud Boots", new Coords(22, 13) ],
			[ "Buckled Boots", new Coords(23, 13) ],
			[ "Riding Boots", new Coords(24, 13) ],
			[ "Snow Boots", new Coords(25, 13) ],

			// items - rings

			[ "Wooden Ring", new Coords(26, 13) ],
			[ "Granite Ring", new Coords(27, 13) ],
			[ "Moonstone Ring", new Coords(28, 13) ],
			[ "Clay Ring", new Coords(29, 13) ],
			[ "Shiny Ring", new Coords(30, 13) ],
			[ "Black Onyx Ring", new Coords(31, 13) ],
			[ "Opal Ring", new Coords(32, 13) ],
			[ "Tiger Eye Ring", new Coords(33, 13) ],
			[ "Emerald Ring", new Coords(34, 13) ],
			[ "Engagement Ring", new Coords(36, 13) ],
			[ "Bronze Ring", new Coords(37, 13) ],
			[ "Sapphire Ring", new Coords(38, 13) ],
			[ "Ruby Ring", new Coords(39, 13) ],
			[ "Diamond Ring", new Coords(0, 14) ],
			[ "Pearl Ring", new Coords(1, 14) ],
			[ "Iron Ring", new Coords(2, 14) ],
			[ "Brass Ring", new Coords(3, 14) ],
			[ "Copper Ring", new Coords(4, 14) ],
			[ "Twisted Ring", new Coords(5, 14) ],
			[ "Steel Ring", new Coords(6, 14) ],
			[ "Agate Ring", new Coords(7, 14) ],
			[ "Silver Ring", new Coords(8, 14) ],
			[ "Gold Ring", new Coords(9, 14) ],
			[ "Topaz Ring", new Coords(10, 14) ],
			[ "Ivory Ring", new Coords(11, 14) ],
			[ "Wire Ring", new Coords(12, 14) ],
			[ "Jade Ring", new Coords(13, 14) ],
			[ "Coral Ring", new Coords(14, 14) ],

			// items - scrolls

			[ "Scroll Titled 'Andova Begarin'", new Coords(10, 17) ],
			[ "Scroll Titled 'Daiyen Fooels'", new Coords(11, 17) ],
			[ "Scroll Titled 'Duam Xnaht'", new Coords(12, 17) ],
			[ "Scroll Titled 'Eblib Yloh'", new Coords(13, 17) ],
			[ "Scroll Titled 'Elam Ebow'", new Coords(14, 17) ],
			[ "Scroll Titled 'Foobie Bletch'", new Coords(15, 17) ],
			[ "Scroll Titled 'Garven Deh'", new Coords(16, 17) ],
			[ "Scroll Titled 'Hackem Muche'", new Coords(17, 17) ],
			[ "Scroll Titled 'Juyed Awk Yacc'", new Coords(18, 17) ],
			[ "Scroll Titled 'Kernod Wel'", new Coords(19, 17) ],
			[ "Scroll Titled 'Kirje'", new Coords(20, 17) ],
			[ "Scroll Titled 'Lep Gex Ven Zea'", new Coords(21, 17) ],
			[ "Scroll Titled 'NR 9'", new Coords(22, 17) ],
			[ "Scroll Titled 'Pratyavayah'", new Coords(23, 17) ],
			[ "Scroll Titled 'Prirutsenie'", new Coords(24, 17) ],
			[ "Scroll Titled 'Read Me'", new Coords(25, 17) ],
			[ "Scroll Titled 'Temov'", new Coords(26, 17) ],
			[ "Scroll Titled 'Tharr'", new Coords(27, 17) ],
			[ "Scroll Titled 'Ve Forbryderne'", new Coords(28, 17) ],
			[ "Scroll Titled 'Velox Neb'", new Coords(29, 17) ],
			[ "Scroll Titled 'Venzar Borgavve'", new Coords(30, 17) ],
			[ "Scroll Titled 'Verr Yed Horre'", new Coords(31, 17) ],
			[ "Scroll Titled 'Xixaxa Xoxaxa Xuxaxa'", new Coords(32, 17) ],
			[ "Scroll Titled 'Yum Yum'", new Coords(33, 17) ],
			[ "Scroll Titled 'Zelgo Mer'", new Coords(34, 17) ],

			// items - spellbooks

			[ "Parchment Spellbook", new Coords(37, 17) ],
			[ "Vellum Spellbook", new Coords(38, 17) ],
			[ "Ragged Spellbook", new Coords(39, 17) ],
			[ "Dogeared Spellbook", new Coords(0, 18) ],
			[ "Mottled Spellbook", new Coords(1, 18) ],
			[ "Stained Spellbook", new Coords(2, 18) ],
			[ "Cloth Spellbook", new Coords(3, 18) ],
			[ "Leather Spellbook", new Coords(4, 18) ],
			[ "White Spellbook", new Coords(5, 18) ],
			[ "Pink Spellbook", new Coords(6, 18) ],
			[ "Red Spellbook", new Coords(7, 18) ],
			[ "Orange Spellbook", new Coords(8, 18) ],
			[ "Yellow Spellbook", new Coords(9, 18) ],
			[ "Velvet Spellbook", new Coords(10, 18) ],
			[ "Light Green Spellbook", new Coords(11, 18) ],
			[ "Dark Green Spellbook", new Coords(12, 18) ],
			[ "Turquoise Spellbook", new Coords(13, 18) ],
			[ "Cyan Spellbook", new Coords(14, 18) ],
			[ "Light Blue Spellbook", new Coords(15, 18) ],
			[ "Dark Blue Spellbook", new Coords(16, 18) ],
			[ "Indigo Spellbook", new Coords(17, 18) ],
			[ "Magenta Spellbook", new Coords(18, 18) ],
			[ "Purple Spellbook", new Coords(19, 18) ],
			[ "Violet Spellbook", new Coords(20, 18) ],
			[ "Tan Spellbook", new Coords(21, 18) ],
			[ "Plaid Spellbook", new Coords(22, 18) ],
			[ "Light Brown Spellbook", new Coords(23, 18) ],
			[ "Dark Brown Spellbook", new Coords(24, 18) ],
			[ "Gray Spellbook", new Coords(25, 18) ],
			[ "Wrinkled Spellbook", new Coords(26, 18) ],
			[ "Dusty Spellbook", new Coords(27, 18) ],
			[ "Bronze Spellbook", new Coords(28, 18) ],
			[ "Copper Spellbook", new Coords(29, 18) ],
			[ "Silver Spellbook", new Coords(30, 18) ],
			[ "Gold Spellbook", new Coords(31, 18) ],
			[ "Glittering Spellbook", new Coords(32, 18) ],
			[ "Shining Spellbook", new Coords(33, 18) ],
			[ "Dull Spellbook", new Coords(34, 18) ],
			[ "Thin Spellbook", new Coords(35, 18) ],
			[ "Thick Spellbook", new Coords(36, 18) ],

			// items - stones

			[ "White Gem", new Coords(27, 19) ],
			[ "White Gem", new Coords(28, 19) ],
			[ "Red Gem", new Coords(29, 19) ],
			[ "Orange Gem", new Coords(30, 19) ],
			[ "Blue Gem", new Coords(31, 19) ],
			[ "Black Gem", new Coords(32, 19) ],
			[ "Green Gem", new Coords(33, 19) ],
			[ "Green Gem", new Coords(34, 19) ],
			[ "Yellow Gem", new Coords(35, 19) ],
			[ "Green Gem", new Coords(36, 19) ],
			[ "Brownish Gem", new Coords(37, 19) ],
			[ "Brownish Gem", new Coords(38, 19) ],
			[ "Black Gem", new Coords(39, 19) ],
			[ "White Gem", new Coords(0, 20) ],
			[ "Yellow Gem", new Coords(1, 20) ],
			[ "Red Gem", new Coords(2, 20) ],
			[ "Violet Gem", new Coords(3, 20) ],
			[ "Red Gem", new Coords(4, 20) ],
			[ "Violet Gem", new Coords(5, 20) ],
			[ "Black Gem", new Coords(6, 20) ],
			[ "Orange Gem", new Coords(7, 20) ],
			[ "Green Gem", new Coords(8, 20) ],
			[ "White Gem", new Coords(9, 20) ],
			[ "Blue Gem", new Coords(10, 20) ],
			[ "Red Gem", new Coords(11, 20) ],
			[ "Brownish Gem", new Coords(12, 20) ],
			[ "Orange Gem", new Coords(13, 20) ],
			[ "Yellow Gem", new Coords(14, 20) ],
			[ "Black Gem", new Coords(15, 20) ],
			[ "Green Gem", new Coords(16, 20) ],
			[ "Violet Gem", new Coords(17, 20) ],
			[ "Gray Stone", new Coords(18, 20) ],
			[ "Gray Stone", new Coords(19, 20) ],
			[ "Gray Stone", new Coords(20, 20) ],
			[ "Gray Stone", new Coords(21, 20) ],
			[ "Rock", new Coords(22, 20) ],

			// items - wands

			[ "Glass Wand", new Coords(39, 18) ],
			[ "Balsa Wand", new Coords(0, 19) ],
			[ "Crystal Wand", new Coords(1, 19) ],
			[ "Maple Wand", new Coords(2, 19) ],
			[ "Pine Wand", new Coords(3, 19) ],
			[ "Oak Wand", new Coords(4, 19) ],
			[ "Ebony Wand", new Coords(5, 19) ],
			[ "Marble Wand", new Coords(6, 19) ],
			[ "Tin Wand", new Coords(7, 19) ],
			[ "Brass Wand", new Coords(8, 19) ],
			[ "Copper Wand", new Coords(9, 19) ],
			[ "Silver Wand", new Coords(10, 19) ],
			[ "Platinum Wand", new Coords(11, 19) ],
			[ "Iridium Wand", new Coords(12, 19) ],
			[ "Zinc Wand", new Coords(13, 19) ],
			[ "Aluminum Wand", new Coords(14, 19) ],
			[ "Uranium Wand", new Coords(15, 19) ],
			[ "Iron Wand", new Coords(16, 19) ],
			[ "Steel Wand", new Coords(17, 19) ],
			[ "Hexagonal Wand", new Coords(18, 19) ],
			[ "Short Wand", new Coords(19, 19) ],
			[ "Runed Wand", new Coords(20, 19) ],
			[ "Long Wand", new Coords(21, 19) ],
			[ "Curved Wand", new Coords(22, 19) ],
			[ "Forked Wand", new Coords(23, 19) ],
			[ "Spiked Wand", new Coords(24, 19) ],
			[ "Jeweled Wand", new Coords(25, 19) ],

			// items - tools

			[ "Key", new Coords(32, 14) ],
			[ "Lockpick", new Coords(33, 14) ],
			[ "Credit Card", new Coords(34, 14) ],
			[ "Candle", new Coords(35, 14) ],
			[ "Candle2", new Coords(36, 14) ],
			[ "Lantern", new Coords(37, 14) ],
			[ "Oil Lamp", new Coords(38, 14) ],
			[ "Magic Lamp", new Coords(39, 14) ],
			[ "Expensive Camera", new Coords(0, 15) ],
			[ "Mirror", new Coords(1, 15) ],
			[ "Crystal Orb", new Coords(2, 15) ],
			[ "Eyeglasses", new Coords(3, 15) ],
			[ "Blindfold", new Coords(4, 15) ],
			[ "Towel", new Coords(5, 15) ],
			[ "Saddle", new Coords(6, 15) ],
			[ "Leash", new Coords(7, 15) ],
			[ "Stethoscope", new Coords(8, 15) ],
			[ "Tinning Kit", new Coords(9, 15) ],
			[ "Tin Opener", new Coords(10, 15) ],
			[ "Can of Grease", new Coords(11, 15) ],
			[ "Figurine", new Coords(12, 15) ],
			[ "Magic Marker", new Coords(13, 15) ],
			[ "Unarmed Land Mine", new Coords(14, 15) ],
			[ "Unarmed Bear Trap", new Coords(15, 15) ],
			[ "Tin Whistle", new Coords(16, 15) ],
			[ "Magic Whistle", new Coords(17, 15) ],
			[ "Flute", new Coords(18, 15) ],
			[ "Flute2", new Coords(19, 15) ],
			[ "Tooled Horn", new Coords(20, 15) ],
			[ "Horn of Cold", new Coords(21, 15) ],
			[ "Horn of Plenty", new Coords(22, 15) ],
			[ "Horn4", new Coords(23, 15) ],
			[ "Harp", new Coords(24, 15) ],
			[ "Harp2", new Coords(25, 15) ],
			[ "Bell", new Coords(26, 15) ],
			[ "Trumpet", new Coords(27, 15) ],
			[ "Drum", new Coords(28, 15) ],
			[ "Earthquake Drum", new Coords(29, 15) ],
			[ "Pickaxe", new Coords(30, 15) ],
			[ "Grappling Hook", new Coords(31, 15) ],
			[ "Unicorn Horn", new Coords(32, 15) ],
			[ "Candelabra", new Coords(33, 15) ],
			[ "Bell of Opening", new Coords(34, 15) ],

			// items - weapons

			[ "Arrow", new Coords(10, 10) ],
			[ "Silver Arrow", new Coords(10, 10) ],
			[ "Battle Axe", new Coords(22, 10) ],
			[ "Hand Axe", new Coords(21, 10) ],
			[ "Bow", new Coords(19, 11) ],
			[ "Bow2", new Coords(20, 11) ],
			[ "Bow3", new Coords(21, 11) ],
			[ "Bow4", new Coords(22, 11) ],
			[ "Sling", new Coords(23, 11) ],
			[ "Crossbow", new Coords(24, 11) ],
			[ "Crossbow Bolt", new Coords(16, 10) ],
			[ "Dagger", new Coords(12, 10) ],
			[ "Elven Dagger", new Coords(13, 10) ],
			[ "Orcish Dagger", new Coords(11, 10) ],
			[ "Silver Dagger", new Coords(14, 10) ],
			[ "Knife", new Coords(17, 10) ],
			[ "Polearm1", new Coords(10, 10) ],
			[ "Rapier0?", new Coords(15, 10) ],
			[ "Rapier1?", new Coords(18, 10) ],
			[ "Rapier2?", new Coords(20, 10) ],
			[ "Sword", new Coords(23, 10) ],
			[ "WormTooth", new Coords(19, 10) ],

			// movers

			[ "Rogue", new Coords(25, 8) ],
			[ "Player1", new Coords(26, 8) ],
			[ "Player2", new Coords(27, 8) ],

			[ "Goblin", new Coords(32, 1) ],
		];

		for (var i = 0; i < tileNamesAndPositions.length; i++)
		{
			var tileNameAndPosition = tileNamesAndPositions[i];
			var tileName = tileNameAndPosition[0];
			var tilePos = tileNameAndPosition[1];

			var image = imagesForTiles[tilePos.y][tilePos.x];
			image.name = tileName;
			returnValue.push(image);
			returnValue[tileName] = image;
		}

		var agentNames = DemoData.buildAgentDatas();

		var tilePos = new Coords(0, 0);
		var imageSizeInTiles = new Coords(40, 27);

		for (var i = 0; i < agentNames.length; i++)
		{
			var tileName = agentNames[i][0];
			var image = imagesForTiles[tilePos.y][tilePos.x];
			image.name = tileName;
			returnValue.push(image);
			returnValue[tileName] = image;			

			tilePos.x++;

			if (tilePos.x >= imageSizeInTiles.x)
			{
				tilePos.y++;
				tilePos.x = 0;
			}
		}

		var imagesForReticlesClockwiseFromE = [];

		var reticlePixelSetsAsStringArrays =  
		[
			[
				"................",
				"................",
				"................",
				"................",
				"............w...",
				"............ww..",
				"............w.w.",
				"............w..w",
				"............w.w.",
				"............ww..",
				"............w...",
				"................",
				"................",
				"................",
				"................",
				"................",
			],

			[
				"................",
				"................",
				"................",
				"................",
				"................",
				"................",
				"................",
				"................",
				"................",
				"................",
				"................",
				"................",
				".....wwwwwww....",
				"......w...w.....",
				".......w.w......",
				"........w.......",
			],

			[
				"................",
				"................",
				"................",
				"................",
				"................",
				"...w............",
				"..ww............",
				".w.w............",
				"w..w............",
				".w.w............",
				"..ww............",
				"...w............",
				"................",
				"................",
				"................",
				"................",
			],

			[
				"........w.......",
				".......w.w......",
				"......w...w.....",
				".....wwwwwww....",
				"................",
				"................",
				"................",
				"................",
				"................",
				"................",
				"................",
				"................",
				"................",
				"................",
				"................",
				"................",
			],
		];

		for (var i = 0; i < reticlePixelSetsAsStringArrays.length; i++)
		{
			var imageName = "Reticle" + i;

			var pixelsAsStrings = reticlePixelSetsAsStringArrays[i];

			var imageForReticle = ImageHelper.buildImageFromStrings
			(
				imageName,
				pixelsAsStrings
			);

			returnValue[imageName] = imageForReticle;
		}

		return returnValue;
	}

	DemoData.buildFont = function()
	{
		var characterImages = ImageHelper.buildImagesFromStringArrays
		(
			"Font",
			[
			[
				".rrr..",
				"r...r.",
				"rrrrr.",
				"r...r.",
				"r...r.",
			],

			[
				"rrrr..",
				"r...r.",
				"rrrr..",
				"r...r.",
				"rrrr..",
			],
			[
				".rrrr.",
				"r.....",
				"r.....",
				"r.....",
				".rrrr.",
			],
			[
				"rrrr..",
				"r...r.",
				"r...r.",
				"r...r.",
				"rrrr..",
			],
			[
				"rrrrr.",
				"r.....",
				"rrrr..",
				"r.....",
				"rrrrr.",
			],
			[
				"rrrrr.",
				"r.....",
				"rrrr..",
				"r.....",
				"r.....",
			],
			[
				".rrrr.",
				"r.....",
				"r..rr.",
				"r...r.",
				".rrrr.",
			],
			[
				"r...r.",
				"r...r.",
				"rrrrr.",
				"r...r.",
				"r...r.",
			],
			[
				"rrrrr.",
				"..r...",
				"..r...",
				"..r...",
				"rrrrr.",
			],
			[
				".rrrrr.",
				"....r..",
				"....r..",
				".r..r..",
				"..rr...",
			],
			[
				"r...r.",
				"r..r..",
				"rrr...",
				"r..r..",
				"r...r.",
			],
			[
				"r.....",
				"r.....",
				"r.....",
				"r.....",
				"rrrrr.",
			],
			[
				"r...r.",
				"rr.rr.",
				"r.r.r.",
				"r...r.",
				"r...r.",
			],
			[
				"r...r.",
				"rr..r.",
				"r.r.r.",
				"r..rr.",
				"r...r.",
			],
			[
				".rrr..",
				"r...r.",
				"r...r.",
				"r...r.",
				".rrr..",
			],
			[
				"rrrr..",
				"r...r.",
				"rrrr..",
				"r.....",
				"r......",
			],
			[
				".rrr..",
				"r...r.",
				"r...r.",
				".rrr..",
				"..r...",
			],
			[
				"rrrr..",
				"r...r.",
				"rrrr..",
				"r..r..",
				"r...r.",
			],
			[
				".rrrr.",
				"r.....",
				".rrr..",
				"....r.",
				"rrrr..",
			],
			[
				"rrrrr.",
				"..r...",
				"..r...",
				"..r...",
				"..r...",
			],
			[
				"r...r.",
				"r...r.",
				"r...r.",
				"r...r.",
				".rrr..",
			],
			[
				"r...r.",
				"r...r.",
				".r.r..",
				".r.r..",
				"..r...",
			],
			[
				"r...r.",
				"r...r.",
				"r.r.r.",
				"rr.rr.",
				"r...r.",
			],
			[
				"r...r.",
				".r.r..",
				"..r...",
				".r.r..",
				"r...r.",
			],
			[
				"r...r.",
				".r.r..",
				"..r...",
				"..r...",
				"..r...",
			],
			[
				"rrrrr.",
				"...r..",
				"..r...",
				".r....",
				"rrrrr.",
			],

			// space

			[
				"......",
				"......",
				"......",
				"......",
				"......",
			],

			// numerals

			[
				".rrr..",
				"r...r.",
				"r.r.r.",
				"r...r.",
				".rrr..",
			],
			[
				"..r...",
				".rr...",
				"..r...",
				"..r...",
				"rrrrr.",
			],
			[
				"rrrr..",
				"....r.",
				".rrrr.",
				"r.....",
				"rrrrr.",
			],
			[
				"rrrr..",
				"....r.",
				"..rr..",
				"....r.",
				"rrrr..",
			],
			[
				"r...r.",
				"r...r.",
				"rrrrr.",
				"....r.",
				"....r.",
			],
			[
				"rrrrr.",
				"r.....",
				"rrrr..",
				"....r.",
				"rrrr..",
			],
			[
				".rrr..",
				"r.....",
				"rrrr..",
				"r...r.",
				".rrr..",
			],
			[
				"rrrrr.",
				"....r.",
				"....r.",
				"....r.",
				"....r.",
			],
			[
				".rrr..",
				"r...r.",
				".rrr..",
				"r...r.",
				".rrr..",
			],
			[
				".rrr..",
				"r...r.",
				".rrrr.",
				"....r.",
				"....r.",
			],

			// symbols

			[
				"......",
				"......",
				"......",
				"......",
				"r.....",
			],

			[
				"......",
				"......",
				"rrrrr.",
				"......",
				"......",
			],

			[
				"..g...",
				"..g...",
				"ggggg.",
				"..g...",
				"..g...",
			],

			]
		);

		var returnValue = new Font
		(
			"ABCDEFGHIJKLMNOPQRSTUVWXYZ "
			+ "0123456789"
			+ ".-+",
			new Coords(6, 5), // characterSize
			characterImages
		);

		return returnValue;
	}

}

// run

new Simulation().main();

</script>
</body>
</html>

This entry was posted in Uncategorized and tagged , , , . Bookmark the permalink.

Leave a comment