(function() {

var mapWidth = 768;
var mapHeight = 512;

var gameContainer;		// top container for all game stuff
var mapCanvas;		// canvas used for drawing the map (walls and floor)
var rangeCanvas;	// canvas used for drawing ranges of deflectors, attractors, etc.
var objectContainer;	// container to hold canvas elements/images to represent objects (holes, zappers, etc)
var swarmCanvas;	// canvas to draw our HERO SOLDIER SWARM of GLORY AND MAGNIFICENTNESS
var pointLayer;
var guiContainer;	// container for UI elements (score, level name, etc)
var welcomeContainer;
var fpsText;
var musicIcon;

var inputLayer;	// layer on top of map and game to get input events

var dc = function(tag, className) {
	var el = document.createElement(tag);
	if (className) {
		el.className = className;
	}
	return el;
}

var mapObjects = [];
var targets = [];
var antiTargets = [];
var triggers = [];
var blockBoxes;

var running = false;
var paused = false;
var endgame = false;

var heroSwarm = [];
var enemySwarms = [];

var heroCenterX = 0;
var heroCenterY = 0;

var dead = false;
var score = 0;
var maxNumHeroes = 10;

var diggQueue = [];
var lastDiggDataTime = 0;
var diggTimeOffset = 0;

var minSpawnTime = 2000;
var lastSpawnTime = 0;
var maxEnemies = 5

var weaponCurrent = 0;
var explosions = [];

var pauseDiv;
var pauseImg;
var scoreNums = [];

var timeNums = [];
var time = 0;
var timePassed = 0;
var lastCycleTime = 0;
var lagTime = 0;
var cycleTime = 1000 / 20;

var godMode = false;

var shooting = false;
var shootingActive = false;
var shootStopTime = 0;
var stopShootingTimer = 0;

var keys = [];
var mouseX = 0;
var mouseY = 0;


var deadScreen;
var deadImg;
var deadText;

var levelNum = 0;

var map;

var levelScreen;
var levelImg;
var levelText;
var levelNum0;
var levelNum1;
var waitingForNextLevel = false;
var waitingForGameStart = true;

var frameCount = 0;
var lastFPSTime = 0;

var firstRun = true;

function loadMap(mapNum) {

	waitingForNextLevel = true;

	if (shooting) {
		Game.Sound.stopSound(Game.Weapons[weaponCurrent].sound);
		Game.Sound.stopSound(Game.Weapons[weaponCurrent].sound+"_buddy");
		shooting = false;
		shootingActive = false;
	}

	map = Game.Levels[mapNum];

	// reset dimensions / clear all canvases
	mapCanvas.width = rangeCanvas.width = swarmCanvas.width = mapWidth;
	mapCanvas.height = rangeCanvas.height = swarmCanvas.height = mapHeight;

	rangeCanvas.style.display = "none";
	rangeCanvas.getContext("2d").globalAlpha = 0.5;

	triggers = [];
	blockBoxes = [];
	for (var i=0;i<mapObjects.length;i++) {
		mapObjects[i].clear();
	}
	mapObjects = [];

	heroSwarm = [];
	enemySwarms = [];

	blockBoxes = [];
	targets = [];
	antiTargets = [];
	triggers = [];

	objectContainer.innerHTML = "";
	pointLayer.innerHTML = "";


	drawMap(map);

	levelScreen = dc("div", "nextLevelScreen fullMap");
	levelImg = dc("img", "nextLevelImg");
	levelImg.src = "level.png";
	guiContainer.appendChild(levelScreen);
	guiContainer.appendChild(levelImg);
	levelText = dc("div", "nextLevelScreenText");
	levelText.innerHTML = "- " + map.description + " -"
	guiContainer.appendChild(levelText);

	levelNum0 = dc("div", "nextLevelNum0");
	levelNum1 = dc("div", "nextLevelNum1");
	var levelNumImg0 = dc("img");
	var levelNumImg1 = dc("img");
	levelNumImg0.src = levelNumImg1.src = "numbers.png";
	levelNum0.appendChild(levelNumImg0);
	levelNum1.appendChild(levelNumImg1);

	levelNumImg0.style.position = "absolute";
	levelNumImg0.style.left = -(((levelNum+1)/10)|0) * 32 + "px";

	levelNumImg1.style.position = "absolute";
	levelNumImg1.style.left = -((levelNum+1) % 10) * 32 + "px";

	guiContainer.appendChild(levelNum0);
	guiContainer.appendChild(levelNum1);

	time = map.time;
	updateTime();

}



function drawMap(map) {

	var mapCtx = mapCanvas.getContext("2d");

	var gridSize = 32;

	mapCtx.lineWidth = 1;
	for (x=0;x<mapWidth;x+=gridSize/4) {
		mapCtx.strokeStyle = x % gridSize ? "#222244" : "#333366";

		mapCtx.beginPath();
		mapCtx.moveTo(x-0.5,0);
		mapCtx.lineTo(x-0.5,mapHeight);
		mapCtx.closePath();
		mapCtx.stroke();
	}
	for (y=0;y<mapWidth;y+=gridSize/4) {
		mapCtx.strokeStyle = y % gridSize ? "#222244" : "#333366";

		mapCtx.beginPath();
		mapCtx.moveTo(0,y-0.5);
		mapCtx.lineTo(mapWidth,y-0.5);
		mapCtx.closePath();
		mapCtx.stroke();
	}

	var shapes = map.shapes;

	for (var i=0;i<shapes.length;i++) {
		var shape = shapes[i];
		switch (shape.shape) {
			case "Box" : 
				drawBox(mapCtx, shape);
				break;
		}
		if (shape.blocking) {
			switch (shape.shape) {
				case "Box" : 
					blockBoxes.push([shape.x, shape.y, shape.w, shape.h]);
					break;
			}
		}
	}

	var objects = map.objects;
	for (var i=0;i<objects.length;i++) {
		var object = objects[i];

		if (Game.MapObjects[object.type]) {
			var obj = Game.MapObjects[object.type]();

			for (var a in object) {
				if (a != "type" && object.hasOwnProperty(a)) {
					obj[a] = object[a];
				}
			}

			mapObjects.push(obj);
			obj.draw();

			if (obj.rangeColor) {
				var rangeCtx = rangeCanvas.getContext("2d");
				rangeCtx.beginPath();
				rangeCtx.strokeStyle = obj.rangeColor;
				rangeCtx.arc(obj.x, obj.y, obj.range, 0, Math.PI * 2, true);
				rangeCtx.closePath();
				rangeCtx.stroke();
			}

			if (obj.getTriggers) {
				var objTriggers = obj.getTriggers();
				for (var j=0;j<objTriggers.length;j++) {
					triggers.push(objTriggers[j]);
				}
			}
		}
	}
}


function drawBox(ctx, shape)
{
	if (shape.border && shape.bordersize) {
		ctx.fillStyle = shape.color;
		ctx.fillRect(shape.x+shape.bordersize, shape.y+shape.bordersize, shape.w-shape.bordersize*2, shape.h-shape.bordersize*2);

		ctx.strokeStyle = shape.border;
		ctx.lineWidth = shape.bordersize;
		ctx.strokeRect(shape.x+shape.bordersize-0.5, shape.y+shape.bordersize-0.5, shape.w - shape.bordersize*2, shape.h - shape.bordersize*2);
	} else {
		ctx.fillStyle = shape.color;
		ctx.fillRect(shape.x, shape.y, shape.w, shape.h);
	}
}


function startGame(map) 
{
	Game.seed(map.seed);

	if (map.music)
		Game.Sound.playMusic(map.music, map.musicStart);

	swarmCanvas.width = mapWidth;
	swarmCanvas.height = mapHeight;
	swarmCanvas.style.width = mapWidth+"px";
	swarmCanvas.style.height = mapHeight+"px";

	Game.Swarm.initSwarm(heroSwarm, map.start.x, map.start.y, 0, map.heroes, Game.SwarmTypes.Hero);

	enemySwarms = [];
	explosions = [];

	timePassed = 0;
	lastCycleTime = new Date().getTime();

	running = true;

	if (firstRun) {
		cycle();
	
		Digg.setAPIKey("http://www.nihilogic.dk/labs/digg_swarm/");
		getDiggData();
		firstRun = false;
	} 
}


function getDiggData() {
	if (dead || waitingForNextLevel || paused) {
		setTimeout(getDiggData, 5000);
		return;
	}
	Digg.callMethod(
		"/stories/diggs",
		{
			count : 50,
			min_date : lastDiggDataTime
		},
		onDiggDataDiggs
	);
}

function onDiggDataDiggs(res) {
	lastDiggDataTime = res.timestamp;
	diggTimeOffset = (new Date().getTime())/1000 - lastDiggDataTime;

	var diggs = res.diggs;
	var stories = [];
	if (diggs) {
		for (var i=0;i<diggs.length;i++) {
			stories[i] = diggs[i].story;
		}
	}

	if (stories.length > 0) {
		Digg.callMethod(
			"/stories/" + stories.join(","),
			{
				sort : "submit_date-desc"
			},
			onDiggDataStories
		);
	} else {
		onDiggDataStories({stories:[]});
	}
}

function onDiggDataStories(res) {
	var stories = res.stories;
	for (var i=0;i<stories.length;i++) {
		if (stories[i].diggs > 5)
			diggQueue.push(stories[i]);
	}

	// current time according to Digg
	var time = Math.floor(new Date().getTime()/1000) - diggTimeOffset;

	if (time - lastDiggDataTime > 20) {
		getDiggData();
	} else {
		setTimeout(getDiggData, diggQueue.length == 0 ? 5000 : ((20-time+lastDiggDataTime)*1000) );
	}
}


function checkDiggQueue() {
	if (new Date().getTime() - lastSpawnTime > minSpawnTime) {
		if (diggQueue.length > 0) {
			var numEnemies = 0;
			for (var i=0;i<enemySwarms.length;i++) {
				numEnemies += enemySwarms[i].length;
			}
			if (numEnemies < maxEnemies) {
				var story = diggQueue.shift();
				spawnEnemy(story);
				lastSpawnTime = new Date().getTime();
			}
		}
	}
}

function spawnEnemy(story) {
	//return;

	var title = story.title;
	if (title.length > 50) title = title.substring(0,40)+"...";
	updateBottomText(
		story.user.name + " dugg "
		+ "<a href='" + story.href + "' "
		+ "title=\"[" + story.container.name + " -> " + story.topic.name + "] " + story.title + "\" "
		+ "target='_blank'>\"" + title + "\"</a>"
		+ "(" + story.diggs + " digg" + (story.diggs==1?"":"s") + ")"
	);

	var numMembers = 1;
	var enemyType;

	if (story.diggs > 500) {
		enemyType = Game.SwarmTypes.Biggie;
	} else if (story.diggs > 50) {
		numMembers = 1 + (story.diggs > 200 ? 1 : 0);
		enemyType = Game.SwarmTypes.Predator;
	} else {
		enemyType = Game.SwarmTypes.Antihero;
		numMembers = Math.min(Math.max(Math.ceil(story.diggs / 3), 6), 2);
	}

	//enemyType = Game.SwarmTypes.Predator;
	//numMembers = 1;

	var swarm = [];
	Game.Swarm.initSwarm(swarm, 
		Math.random() > 0.5 ? 868 : -100, 
		64 + Math.random()*12*32, 
		Math.PI, 
		numMembers, enemyType
	);
	swarm.points = Math.round( Math.sqrt(story.diggs * 10000) / numMembers );
	enemySwarms.push(swarm);


}


function setOpacity(oElement, fAlpha)
{
	oElement.style.opacity = fAlpha;
}


function togglePause() {
	if (dead || waitingForNextLevel || endgame) return;

	if (!paused) {
		Game.Sound.pauseMusic();
		//Game.Sound.playSound("music_pause", true);
		shooting = false;

		if (!pauseDiv) {
			pauseDiv = dc("div", "pauseDiv fullMap");
			pauseImg = dc("img", "pauseImg");
			pauseImg.src = "pause.png";
		}

		rangeCanvas.style.display = "block";

		guiContainer.appendChild(pauseDiv);
		guiContainer.appendChild(pauseImg);
		paused = true;
	} else {
		Game.Sound.unpauseMusic();
		Game.Sound.stopSound("music_pause");

		rangeCanvas.style.display = "none";

		guiContainer.removeChild(pauseDiv);
		guiContainer.removeChild(pauseImg);
		
		lastCycleTime = new Date().getTime();
		paused = false;
	}
}


function toggleDeadScreen() 
{
	//Game.Sound.pauseMusic();
	//Game.Sound.playSound("music_dead", true);
	shooting = false;

	deadScreen = dc("div", "deadScreen fullMap");

	deadImg = dc("img", "deadImg");
	deadImg.src = "ohno.png";

	guiContainer.appendChild(deadScreen);
	guiContainer.appendChild(deadImg);

	deadText = dc("div", "deadScreenText");
	deadText.innerHTML = "- Click mouse to place reinforcement heroes -"
	guiContainer.appendChild(deadText);

	paused = true;

}


function nextLevel() {
	//Game.Sound.stopMusic();
	//Game.Sound.playSound("music_level", true);
	shooting = false;

	paused = true;

	levelNum++;

	if (levelNum >= Game.Levels.length) {
		victory();
	} else {
		loadMap(levelNum);
	}
}


function victory() {
	victoryScreen = dc("div", "victoryScreen fullMap");
	victoryImg = dc("img", "victoryImg");
	victoryImg.src = "victory.png";
	guiContainer.appendChild(victoryScreen);
	guiContainer.appendChild(victoryImg);

	victoryText = dc("div", "victoryScreenText");
	victoryText.innerHTML = "- You have achieved internet fame. Press F5 to play again. -"
	guiContainer.appendChild(victoryText);
	endgame = true;
}

function cycle()
{
	frameCount++;

	var now = new Date().getTime();

	if (running) {
		if (!(paused || waitingForNextLevel)) {
			if (!dead) {
				var delta = now - lastCycleTime;
				time -= delta / 1000;
				lastCycleTime = now;
				updateTime();

				if (Math.round(time) == 0) {
					nextLevel();
				}
			}
			if (!waitingForNextLevel) {
				if (dead) {
					toggleDeadScreen();
				}
	
				checkDiggQueue();
	
				// clear swarm canvas
				swarmCanvas.width = mapWidth;

				var ctx = swarmCanvas.getContext("2d");

				if (heroSwarm.length == 0) dead = true;

				if (!dead) {
					// move and render magnificent heroes
					heroSwarm = Game.Swarm.moveSwarm(heroSwarm);
					Game.Swarm.renderSwarm(heroSwarm, ctx, ctx);
	
					var posX = 0;
					var posY = 0;
	
					for (var i=0;i<heroSwarm.length;i++) {
						var hero = heroSwarm[i];
						posX += hero.x;
						posY += hero.y;
					}
					heroCenterX = posX / heroSwarm.length;
					heroCenterY = posY / heroSwarm.length;
				}
	
	
				if (shooting && !shootingActive) {
					if (new Date().getTime() - shootStopTime > 500) {
						shooting = false;
					}
				}
	
				// move and render bad dudes
				for (var i=0;i<enemySwarms.length;i++) {
					enemySwarms[i] = Game.Swarm.moveSwarm(enemySwarms[i]);
					Game.Swarm.renderSwarm(enemySwarms[i], ctx, ctx);
				}
	
				if (shooting) {
					renderWeaponFire();
				}
	
				if (explosions.length)
					renderExplosions();
	
				checkEnemyKills();
	
				renderPointText();


			}
		}
	}

	//now = new Date().getTime();
	//var nextTime = Math.min(cycleTime,Math.max(1,(cycleTime - (now - lagTime))));

	nextTime = cycleTime;
	setTimeout(cycle, nextTime);

	lagTime = now;

}

function init() 
{


	gameContainer = document.getElementById("game");
	gameContainer.className = "gameContainer";

	mapCanvas = dc("canvas", "mapCanvas fullMap");
	rangeCanvas = dc("canvas", "rangeCanvas fullMap");
	objectContainer = dc("div", "objectContainer fullMap");
	swarmCanvas = dc("canvas", "swarmCanvas fullMap");
	pointLayer = dc("div", "pointLayer fullMap");
	guiContainer = dc("div", "guiContainer fullMap");
	inputLayer = dc("div", "inputLayer fullMap");

	welcomeContainer = dc("div", "welcomeContainer");
	welcomeContainer.innerHTML = "<img src='digg_attack.png'><br>A Digg-'em-up struggle against the overwhelming forces of social media<br><br>Get highest score before time runs out and you proceed to next level.<br>Enemies are spawned when stories are dugg<br>Eliminate enemies to gain points and more heroes<br>If all heroes die, reinforcements are sent in (cost = 25% of score).<br><br>Press 'P' or 'Space' to pause game, 'S' to toggle sound<br>Press 'Ctrl' to shoot, click to place \"move-to\" targets<br>Right or shift-click to place antitargets<br><br><blink>Click anywhere to start game</blink>"

	fpsText = dc("div", "fpsText");
	fpsText.style.display = "none";

	guiContainer.appendChild(fpsText);

	gameContainer.appendChild(mapCanvas);
	gameContainer.appendChild(objectContainer);
	gameContainer.appendChild(rangeCanvas);
	gameContainer.appendChild(swarmCanvas);
	gameContainer.appendChild(pointLayer);
	gameContainer.appendChild(guiContainer);
	gameContainer.appendChild(welcomeContainer);
	gameContainer.appendChild(inputLayer);

	setupUI();

	registerInputEvents();

	setInterval(
		function() {
			var now = new Date().getTime();
			var delta = (now - lastFPSTime) / 1000;
			fpsText.innerHTML = "FPS: " + Math.round((frameCount / delta)*10)/10;
			frameCount = 0;
			lastFPSTime = now;
		}, 1000
	);

}

var bottomText;

function setupUI() {
	var topBar = dc("div");
	topBar.style.position = "absolute";
	topBar.style.left = "0px";
	topBar.style.width = "100%";
	topBar.style.height = "32px";
	topBar.style.borderBottom = "1px solid rgb(80,80,128)";

	var topBarBG = dc("div");
	topBarBG.style.backgroundColor = "rgb(46,46,92)";
	topBarBG.style.position = "absolute";
	topBarBG.style.width = "100%";
	topBarBG.style.height = "32px";
	setOpacity(topBarBG, 0.5);
	topBar.appendChild(topBarBG);

	var scoreImg = dc("img");
	scoreImg.style.position = "absolute";
	scoreImg.src = "score.png";
	scoreImg.style.left = "388px";
	topBar.appendChild(scoreImg);

	var scoreCtr = dc("div");
	scoreCtr.style.position = "absolute";
	scoreCtr.style.left = "544px";	
	scoreCtr.style.width = 7*32 + "px";
	scoreCtr.style.height = "32px";
	topBar.appendChild(scoreCtr);

	for (var i=0;i<7;i++) {
		var numCtr = dc("div");
		numCtr.style.position = "absolute";
		numCtr.style.overflow = "hidden";
		numCtr.style.left = i*32 + "px";
		numCtr.style.width = "32px";
		numCtr.style.height = "32px";

		var numImg = dc("img");
		numImg.src = "numbers.png";
		numImg.style.position = "absolute";
		numImg.style.width = "320px";
		numImg.style.height = "32px";
		numCtr.appendChild(numImg);

		scoreCtr.appendChild(numCtr);
		scoreNums[i] = numImg;
	}

	var timeImg = dc("img");
	timeImg.style.position = "absolute";
	timeImg.style.left = "5px";
	timeImg.src = "time.png";
	topBar.appendChild(timeImg);

	var timeCtr = dc("div");
	timeCtr.style.position = "absolute";
	timeCtr.style.left = "128px";	
	timeCtr.style.width = 7*32 + "px";
	timeCtr.style.height = "32px";
	topBar.appendChild(timeCtr);

	for (var i=0;i<3;i++) {
		var numCtr = dc("div");
		numCtr.style.position = "absolute";
		numCtr.style.overflow = "hidden";
		numCtr.style.left = i*32 + "px";
		numCtr.style.width = "32px";
		numCtr.style.height = "32px";

		var numImg = dc("img");
		numImg.src = "numbers.png";
		numImg.style.position = "absolute";
		numImg.style.width = "320px";
		numImg.style.height = "32px";
		numCtr.appendChild(numImg);

		timeCtr.appendChild(numCtr);
		timeNums[i] = numImg;
	}

	guiContainer.appendChild(topBar);

	// bottom
	var bottomBar = dc("div", "bottomBar");
	var bottomBarBG = dc("div", "bottomBarBG");
	bottomBar.appendChild(bottomBarBG);

	bottomText = dc("span", "bottomText");

	setOpacity(bottomText, 0.5);
	bottomBar.appendChild(bottomText);

	musicIcon = dc("img", "musicIcon");
	musicIcon.src = "music.png";
	musicIcon.onmouseover = function() {this.style.opacity = 0.8};
	musicIcon.onmouseout = function() {this.style.opacity = 0.4};
	musicIcon.onclick = toggleMusicEnabled;
	bottomBar.appendChild(musicIcon);

	gameContainer.appendChild(bottomBar);
}

function updateBottomText(txt) {
	bottomText.innerHTML = txt;
}

function updateScore() {
	var scoreStr = score+"";
	while (scoreStr.length < 7) scoreStr = "0" + scoreStr;
	for (var i=0;i<7;i++) {
		scoreNums[i].style.left = -(parseInt(scoreStr.substr(i,1),10)*32)+"px";
	}
}

function updateTime() {
	var timeStr = Math.round(time)+"";
	while (timeStr.length < timeNums.length) timeStr = "0" + timeStr;
	for (var i=0;i<timeNums.length;i++) {
		timeNums[i].style.left = -(parseInt(timeStr.substr(i,1),10)*32)+"px";
	}
}


function addEvent(oObject, strEvent, fncAction) {
	if (oObject.addEventListener) { 
		oObject.addEventListener(strEvent, fncAction, false); 
	} else if (oObject.attachEvent) { 
		oObject.attachEvent("on" + strEvent, fncAction); 
	}
}

function registerInputEvents() 
{
	addEvent(document, "keydown", onKeyDown);
	addEvent(document, "keyup", onKeyUp);
	addEvent(inputLayer, "contextmenu", onContextMenu);
	addEvent(inputLayer, "mousedown", onMouseDown);
	addEvent(inputLayer, "mousemove", onMouseMove);
}


var keyString = "";

function onKeyDown(e) 
{
	if (waitingForNextLevel || waitingForGameStart || dead)
		return;

	e = e || window.event;
	var key = e.keyCode;

	keyString += String.fromCharCode(e.keyCode);
	if (keyString.substring(keyString.length-5) == "IDDQD") {
		godMode = !godMode;
		var godImg = dc("img", "godImg");
		godImg.src = "godmode" + (godMode ? "on" : "off") + ".png";
		guiContainer.appendChild(godImg);
		fadeImage(godImg, 1000, 100, 
			function() {
				guiContainer.removeChild(godImg);
			}, false
		);
		keyString = "";
	}

	if (keys[key]) return;

	keys[key] = true;

	//console.log(key);

	switch (key) {
		case 17:	// ctrl;
			if (!paused && !dead) {
				if (stopShootingTimer) {
					clearTimeout(stopShootingTimer);
					stopShootingTimer = 0;
				}
				shooting = true;
				shootingActive = true;
			}
			return false;
			break;
		case 32:	// space, P
		case 80:	// P
			togglePause();
			return false;
			break;
		case 70: 	// F
			fpsText.style.display = fpsText.style.display == "none" ? "block" : "none";
			break;
		case 83:	// S
			toggleMusicEnabled();
			break;
			
	}

}

function toggleMusicEnabled() {
	if (Game.Sound.isMusicEnabled()) {
		if (Game.Sound.getCurrentMusic()) {
			var wasPlaying = Game.Sound.getCurrentMusic();
			Game.Sound.stopMusic(Game.Sound.getCurrentMusic());
			Game.Sound.setCurrentMusic(wasPlaying);
		}
		Game.Sound.setMusicEnabled(false);
		musicIcon.src = "musicoff.png";
	} else {
		Game.Sound.setMusicEnabled(true);
		if (Game.Sound.getCurrentMusic()) {
			Game.Sound.playMusic(Game.Sound.getCurrentMusic());
		}
		musicIcon.src = "music.png";
	}
}

function onKeyUp(e) 
{
	e = e || window.event;
	var key = e.keyCode;
	keys[key] = false;

	switch (key) {
		case 17:	// ctrl;
			if (!paused) {
				shootingActive = false;
				shootStopTime = new Date().getTime();
			}
			break;
	}
}

function onContextMenu(e) 
{
	if (e.preventDefault)
		e.preventDefault();
	return false;
}



function onMouseMove(e) 
{
	e = e || window.event;
	mouseX = e.clientX - objectContainer.offsetLeft + (document.scrollLeft||document.body.scrollLeft);
	mouseY = e.clientY - objectContainer.offsetTop + (document.scrollTop||document.body.scrollTop);
}

function isPointInBlock(x, y, buffer) {
	buffer = buffer || 0;
	for (var i=0;i<blockBoxes.length;i++) {
		var box = blockBoxes[i];
		if (x >= box[0]-buffer && x <= box[0]+box[2]+buffer && y >= box[1]-buffer && y <= box[1]+box[3]+buffer) {
			return true;
		}
	}
	return false;
}

function onMouseDown(e) 
{
	if (dead && deadScreen) {
		if (isPointInBlock(mouseX, mouseY, 5)) {
			return;
		}

		for (var i=0;i<Game.Levels[levelNum].heroes;i++) {
			spawnHero(
				mouseX + (Math.random()-0.5)*16, 
				mouseY + (Math.random()-0.5)*16
			);
		}

		guiContainer.removeChild(deadScreen);
		guiContainer.removeChild(deadImg);
		guiContainer.removeChild(deadText);
		paused = false;
		dead = false;
		//Game.Sound.unpauseMusic();

		score = Math.round(score/4*3);
		updateScore();

		lastCycleTime = new Date().getTime();

	}

	if (waitingForNextLevel) {
		guiContainer.removeChild(levelScreen);
		guiContainer.removeChild(levelImg);
		guiContainer.removeChild(levelText);
		guiContainer.removeChild(levelNum0);
		guiContainer.removeChild(levelNum1);
		waitingForNextLevel = false;
		paused = false;
		startGame(Game.Levels[levelNum]);
		return;
	}

	if (paused) {
		return;
	}

	e = e || window.event;

	if (running) {
		var x = mouseX;
		var y = mouseY;
		if (e.button == 2 || e.button == 0 && e.shiftKey) { // right or shift+lmb
			addAntiTarget(x, y);
		} else if (e.button == 0) { // left
			addTarget(x, y);
		}
	}

	if (waitingForGameStart) {
		welcomeContainer.style.display = "none";
		loadMap(levelNum);
		waitingForGameStart = false;
	}

}

function arrayRemoveObject(arr, item)
{
	var newArr = [];
	for (var i=0;i<arr.length;i++) {
		if (arr[i] != item)
			newArr.push(arr[i])
	}
	return newArr;
}

function setOpacity(element, alpha) {
	element.style.opacity = alpha;
}


function fadeImage(img, time, timeStep, callback, canPause) {
	var alpha = 1;
	var step = timeStep / time;
	var fade = function() {
		if (!img) return;

		if (!paused || !canPause) {
			alpha -= step;
			alpha = Math.max(alpha,0);
			setOpacity(img, alpha);
		}
		if (alpha) {
			setTimeout(fade, timeStep);
		} else {
			if (callback) callback();
		}
	}
	setTimeout(fade, timeStep);
}

// temporary alternative, image size is reduced instead of alpha. Chrome does not like opacity + transparent pixels, it seems.
function fadeImage(img, time, timeStep, callback, canPause) {

	var size = 1;
	var step = timeStep / time;

	img._fadeOrgWidth = parseInt(img.style.width,10);
	img._fadeOrgHeight = parseInt(img.style.height,10);
	img._fadeOrgLeft = parseInt(img.style.left,10);
	img._fadeOrgTop = parseInt(img.style.top,10);

	var fade = function() {
		if (!img) return;

		if (!paused || !canPause) {
			size -= step;
			size = Math.max(size,0);

			var w = (img._fadeOrgWidth * size);
			var h = (img._fadeOrgHeight * size);
			var dw = img._fadeOrgWidth - w;
			var dh = img._fadeOrgHeight - h;
			img.style.width =  w + "px";
			img.style.height = h + "px";

			img.style.left = Math.round(img._fadeOrgLeft + dw/2) + "px";
			img.style.top = Math.round(img._fadeOrgTop + dh/2) + "px";

			//setOpacity(img, size);
		}
		if (size) {
			setTimeout(fade, timeStep);
		} else {
			if (callback) callback();
		}
	}
	setTimeout(fade, timeStep);
}

var targetProto;
var antiTargetProto;

function addTarget(x, y) {
	if (!targetProto) {
		var canvas = dc("canvas");
		canvas.width = canvas.height = 64;

		var ctx = canvas.getContext("2d");

		for (var i=1;i<8;i++) {
			ctx.beginPath();
			ctx.arc(32, 32, i*3, 0,Math.PI*2,true);
			ctx.closePath();
			ctx.fillStyle = "rgba(70,250,70," + (window.opera ? 0.2 : 0.08) + ")";
			ctx.fill();
		}

		ctx.fillStyle = "rgb(255,255,255)";
		ctx.fillRect(26.5,26.5,11,11);

		ctx.save();
		ctx.translate(32,32);
		ctx.rotate(0.25 * Math.PI);
		ctx.translate(-32,-32);
		ctx.fillRect(26,26,12,12);
		ctx.restore();

		for (var i=1;i<3;i++) {
			ctx.beginPath();
			ctx.arc(32, 32, i*3, 0,Math.PI*2,true);
			ctx.closePath();
			ctx.fillStyle = "rgba(150,250,150," + (window.opera ? 0.2 : 0.1) + ")";
			ctx.fill();
		}
		targetProto = canvas;
	}

	var img = dc("canvas");
	img.width = img.height = 64;
	img.style.width = img.style.height = "64px";
	img.getContext("2d").drawImage(targetProto,0,0);
	img.style.position = "absolute";
	img.style.left = x - 32 + 1;
	img.style.top = y - 32;

	var target = {
		x : x,
		y : y,
		img : img
	};
	targets.push(target);
	
	objectContainer.appendChild(img);

	var removeTime = 2000;
	fadeImage(img, removeTime, 100, 
		function() {
			try {
			objectContainer.removeChild(img);
			} catch(e) {}
			targets = arrayRemoveObject(targets, target);
		}, true
	);
}

function addAntiTarget(x, y)
{

	if (!antiTargetProto) {
		var canvas = dc("canvas");
		canvas.width = canvas.height = 64;

		var ctx = canvas.getContext("2d");

		for (var i=1;i<8;i++) {
			ctx.beginPath();
			ctx.arc(32, 32, i*3, 0,Math.PI*2,true);
			ctx.closePath();
			ctx.fillStyle = "rgba(250,70,70," + (window.opera ? 0.2 : 0.08) + ")";
			ctx.fill();
		}

		ctx.fillStyle = "rgb(255,255,255)";
		ctx.fillRect(26.5,26.5,11,11);

		ctx.save();
		ctx.translate(32,32);
		ctx.rotate(0.25 * Math.PI);
		ctx.translate(-32,-32);
		ctx.fillRect(26.5,26.5,11,11);
		ctx.restore();

		for (var i=1;i<3;i++) {
			ctx.beginPath();
			ctx.arc(32, 32, i*3, 0,Math.PI*2,true);
			ctx.closePath();
			ctx.fillStyle = "rgba(250,150,150," + (window.opera ? 0.2 : 0.1) + ")";
			ctx.fill();
		}

		antiTargetProto = canvas;
	}

	var img = dc("canvas");
	img.width = img.height = 64;
	img.style.width = img.style.height = "64px";
	img.getContext("2d").drawImage(antiTargetProto,0,0);
	img.style.position = "absolute";
	img.style.left = x - 32 + 1;
	img.style.top = y - 32;

	var antiTarget = {
		x : x,
		y : y,
		img : img
	};
	antiTargets.push(antiTarget);
	
	objectContainer.appendChild(img);

	var removeTime = 2000;
	fadeImage(img, removeTime, 100, 
		function() {
			try {
			objectContainer.removeChild(img);
			} catch(e) {}
			antiTargets = arrayRemoveObject(antiTargets, antiTarget);
		}, true
	);
}


function spawnExplosion(x, y, color, pointSize, numPoints, frames, minSpeed, speedMul) {
	var points = [];
	for (var i=0;i<numPoints;i++) {
		var dirX = (Math.random()-0.5)*2;
		var dirY = (Math.random()-0.5)*2;
		var len = Math.sqrt(dirX*dirX + dirY*dirY);
		var speedFactor = Math.random() * (speedMul||1);
		var colorFactor = Math.max(0.3,Math.sin(speedFactor*Math.PI*0.5+0.5*Math.PI));
		points.push(
			{
				x : 0,
				y : 0,
				dirX : dirX/len,
				dirY : dirY/len,
				speed : minSpeed + speedFactor*(1-minSpeed),
				color : [
					Math.round(color[0]*(colorFactor)), 
					Math.round(color[1]*(colorFactor)), 
					Math.round(color[2]*(colorFactor))
				]
			}
		)
	}
	explosions.push(
		{
			x : x,
			y : y,
			pointSize : pointSize,
			points : points,
			frames : frames,
			frameCurrent : 0
		}
	);

}



function renderExplosions() {
	var newExp = [];

	var ctx = swarmCanvas.getContext("2d");

	for (var e=0;e<explosions.length;e++) {
		var exp = explosions[e];
		var points = exp.points;

		var step = exp.frameCurrent / exp.frames;

		var stepSin = Math.sin(step*Math.PI*0.5+0.5*Math.PI);

		for (var i=0;i<points.length;i++) {
			var point = points[i];

			var speed = stepSin * point.speed;
			point.x += point.dirX * speed * 12;
			point.y += point.dirY * speed * 12;

			var alpha = 1;
			if (step > 0.5) {
				alpha = 1-(step - 0.5)*2;
			}

			var color = point.color;
			ctx.fillStyle = "rgba("+color[0]+","+color[1]+","+color[2]+","+alpha+")";
			ctx.fillRect(
				(exp.x + point.x)|0, 
				(exp.y + point.y)|0, 
				exp.pointSize, exp.pointSize
			);

		}

		exp.frameCurrent++;
		if (exp.frameCurrent < exp.frames)
			newExp.push(exp);
	}
	explosions = newExp;
}

function renderWeaponFire() {
	var heroes = heroSwarm;

	var targetX = mouseX;
	var targetY = mouseY;

	var ctx = swarmCanvas.getContext("2d");

	var weapon = Game.Weapons[weaponCurrent];
	var now = new Date().getTime();

	ctx.strokeStyle = weapon.color;
	ctx.lineWidth = weapon.lineWidth;

	ctx.beginPath();

	for (var h=0;h<heroes.length;h++) {
		var hero = heroes[h];

		var heroX = hero.x;
		var heroY = hero.y;

		var dX = targetX - heroX;
		var dY = targetY - heroY;


		var slope = dY / dX;

		var stepSize = weapon.shotSpread;

		var len = Math.sqrt(dX*dX + dY*dY);

		var recoilX = hero.x - dX/len * 2;
		var recoilY = hero.y - dY/len * 2;
		if (!(isPointInBlock(recoilX, recoilY, hero.collisionRadius) || recoilX < 0 || recoilX >= mapWidth || recoilY < 0 || recoilY >= mapHeight)) {
			hero.x = recoilX;
			hero.y = recoilY;
		}

		var steps = len / stepSize;

		var lenStepX = dX / steps;
		var lenStepY = dY / steps;

		len = 1000;

		var x = heroX;
		var y = heroY;


		var start = Math.random();

		x += lenStepX * start;
		y += lenStepY * start;

		if (shootingActive) {
			hero.shootX = hero.x;
			hero.shootY = hero.y;
			hero.shootStepX = lenStepX;
			hero.shootStepY = lenStepY;
			hero.shootStepSize = stepSize;
		} else {
			var deltaTime = now - shootStopTime;
			x = hero.shootX + (hero.shootStepX * len / hero.shootStepSize) * (deltaTime / 500);
			y = hero.shootY + (hero.shootStepY * len / hero.shootStepSize) * (deltaTime / 500);

			lenStepX = hero.shootStepX;
			lenStepY = hero.shootStepY;
			stepSize = hero.shootStepSize;

		}

		var shotSizeX = weapon.shotSize * lenStepX / stepSize;
		var shotSizeY = weapon.shotSize * lenStepY / stepSize;

		var endX = heroX + len * lenStepX / stepSize;
		var endY = heroY + len * lenStepY / stepSize;

		var shootDirX = dX < 0 ? -1 : 1;
		var shootDirY = dY < 0 ? -1 : 1;

		// find the enemies that are in the quadrant of the firing direction
		var enemiesInSight = [];

		for (var i=0;i<enemySwarms.length;i++) {
			var enemies = enemySwarms[i];

			for (var e=0;e<enemies.length;e++) {

				var enemy = enemies[e];

				if (enemy.health < 0) continue;

				var x1 = heroX - enemy.x;
				var y1 = heroY - enemy.y;

				if (x1 > 0 && shootDirX > 0 || x1 < 0 && shootDirX < 0)
					continue;
				if (y1 > 0 && shootDirY > 0 || y1 < 0 && shootDirY < 0)
					continue;

				enemiesInSight.push(
					{
						swarm : enemies, 
						idx : e,
						x : enemy.x,
						y : enemy.y,
						dist : Math.sqrt(x1*x1+y1*y1)
					}
				)

			}
		}

		var enemiesSorted = enemiesInSight.sort(
			function(a, b) {
				return b.dist < a.dist;
			}
		)

		for (var e=0;e<enemiesSorted.length;e++) {
			var enemyDesc = enemiesSorted[e];
			var enemy = enemyDesc.swarm[enemyDesc.idx];

			var x1 = heroX - enemy.x;
			var y1 = heroY - enemy.y;

			var x2 = endX - enemy.x;
			var y2 = endY - enemy.y;

			var dX = x2 - x1;
			var dY = y2 - y1;
			var D = x1*y2 - x2*y1;
			var dR = Math.sqrt(dX*dX+dY*dY);
			var r = enemy.collisionRadius;
			var dsc = r*r * dR*dR - D*D;

			if (dsc > 0) {
				var dscSqrt = Math.sqrt(dsc);
				var dR2 = dR*dR;
				var dYAbs = dY > 0 ? dY : -dY;

				var intX1 = (D*dY + (dY < 0 ? -1 : 1) * dX * dscSqrt) / dR2;
				var intY1 = (-D*dX + dYAbs * dscSqrt) / dR2;

				var intX2 = (D*dY - (dY < 0 ? -1 : 1) * dX * dscSqrt) / dR2;
				var intY2 = (-D*dX - dYAbs * dscSqrt) / dR2;

				intX1 += enemy.x;
				intY1 += enemy.y;

				intX2 += enemy.x;
				intY2 += enemy.y;

				var dX1 = intX1 - heroX;
				var dY1 = intY1 - heroY;
				var dist1 = dX1*dX1+dY1*dY1;

				var dX2 = intX2 - heroX;
				var dY2 = intY2 - heroY;
				var dist2 = dX2*dX2+dY2*dY2;

				if (dist1 < dist2) {
					var intX = intX1;
					var intY = intY1;
				} else {
					var intX = intX2;
					var intY = intY2;
				}

				spawnExplosion(intX, intY, 
					[255, 100 + 130 * Math.random(), 100 + 50 * Math.random()], 
					2, 4, 4, 0
				);


				// calculate new length of shooting ray
				endX = intX;
				endY = intY;
				dX = endX - hero.x;
				dY = endY - hero.y;
				len = Math.sqrt(dX*dX + dY*dY);


				enemy.health -= weapon.damage;
				score += weapon.damage;
				if (enemy.health < 0) {
					killEnemy(enemyDesc.swarm, enemyDesc.idx);
				} else {
					var forceX = enemy.x + dX/len * 3;
					var forceY = enemy.y + dY/len * 3;
					if (!(isPointInBlock(forceX, forceY, enemy.collisionRadius) || forceX < 0 || forceX >= mapWidth || forceY < 0 || forceY >= mapHeight)) {
						enemy.x = forceX;
						enemy.y = forceY;
					}
				}
				updateScore();

				break;
			}
		}

		if (x && y) {
			ctx.moveTo(x, y);
			for (var i=start*stepSize;i<len;i+=stepSize) {
				weapon.draw(ctx, x,y,shotSizeX,shotSizeY);
	
				x += lenStepX;
				y += lenStepY;
			}
		}

	}

	ctx.closePath();
	ctx.stroke();

}

function killEnemy(swarm, idx) {
	var enemy = swarm[idx];

	enemy.explode(enemy.x, enemy.y);

	Game.Swarm.removeSwarmMember(swarm, idx);

	var alive = false;
	for (var i=0;i<swarm.length;i++) {
		if (!swarm[i].removed) alive = true;
	}
	if (!alive) {
		enemySwarmDead(swarm);
	}

	score += swarm.points;

	spawnPointText(enemy.x, enemy.y, swarm.points);

}

function enemySwarmDead(swarm) {
	var newSwarms = [];
	for (var i=0;i<enemySwarms.length;i++) {
		if (enemySwarms[i] != swarm)
			newSwarms.push(enemySwarms[i]);
	}
	enemySwarms = newSwarms;

	spawnHero();
}


function spawnHero(x, y) {
	var numHeroes = heroSwarm.length;
	if (numHeroes < maxNumHeroes) {
	
		if (numHeroes > 0) {
			var buddyIdx = Math.floor(numHeroes * Math.random());
			var buddy = heroSwarm[buddyIdx];
	
			if (!x) x = buddy.x + 1;
			if (!y) y = buddy.y + 1;

			heroSwarm.push(
				Game.Swarm.createSwarmMember(x, y, buddy.angle, Game.SwarmTypes.Hero)
			);
		} else if (x >= 0 && y >= 0) {
			heroSwarm.push(Game.Swarm.createSwarmMember(x, y, Math.random() * Math.PI * 2, Game.SwarmTypes.Hero));
		}
	}
}

function killHero(idx, silent) {


	var hero = heroSwarm[idx];

	if (!silent) {
		Game.Sound.playSound("sfx_explode6", false, 15);
		Game.Sound.playSound("sfx_explode1", false, 60);

		hero.explode(hero.x, hero.y);
		spawnText(hero.x, hero.y, "ow!");
	}

	Game.Swarm.removeSwarmMember(heroSwarm, idx);

	var alive = false;
	for (var i=0;i<heroSwarm.length;i++) {
		if (!heroSwarm[i].removed)
			alive = true;
	}

	if (!alive) {
		shooting = false;
		shootingActive = false;
		dead = true;
		Game.Sound.stopSound("sfx_machinegun");
		Game.Sound.stopSound("sfx_machinegun_buddy");
	}
}

function checkEnemyKills() {
	for (var i=0;i<enemySwarms.length;i++) {
		var enemies = enemySwarms[i];
		for (var e=0;e<enemies.length;e++) {
			var enemy = enemies[e];
			for (var h=0;h<heroSwarm.length;h++) {
				var hero = heroSwarm[h];

				if (hero.removed) continue;

				var hero = heroSwarm[h];
				var x1 = hero.x;
				var y1 = hero.y;

				var x2 = enemy.x;
				var y2 = enemy.y;

				var dX = x2-x1;
				var dY = y2-y1;

				var dist = dX*dX + dY*dY;

				if (dist < enemy.collisionRadius*enemy.collisionRadius && !godMode) {
					killHero(h);
				}
			}
		}
	}
}

var pointTextDivs = [];

function spawnPointText(x, y, points) {
	var pointDiv = dc("div", "pointDiv");
	pointDiv.style.left = x+"px";
	pointDiv.style.top = y+"px";

	pointDiv.innerHTML = points;

	pointLayer.appendChild(pointDiv);

	pointTextDivs.push(
		{
			div : pointDiv,
			frame : 20
		}
	)
}

function spawnText(x, y, text) {
	var textDiv = dc("div", "textDiv");
	textDiv.style.left = x+"px";
	textDiv.style.top = y+"px";

	textDiv.innerHTML = text;

	pointLayer.appendChild(textDiv);

	pointTextDivs.push(
		{
			div : textDiv,
			frame : 20
		}
	)
}

function renderPointText() {
	var newDivs = [];
	for (var i=0;i<pointTextDivs.length;i++) {
		var div = pointTextDivs[i].div;
		var frame = pointTextDivs[i].frame;

		var step = frame / 20;

		div.style.opacity = step;

		pointTextDivs[i].frame--;
		if (frame > 0) {
			newDivs.push(pointTextDivs[i]);
		} else {
			try {
				pointLayer.removeChild(div);
			} catch(e) {}
		}
	}
	pointTextDivs = newDivs;
}



var randomSeed = 0;

Game.random = function()
{
	randomSeed = (randomSeed * 9301 + 49297) % 233280;
	return randomSeed / (233280.0);
};

Game.seed = function(seed) 
{
	randomSeed = seed;
}


Game.getHeroCenter = function() {
	return {
		x : heroCenterX,
		y : heroCenterY
	}
}

Game.addMapObjectElement = function(el) {
	objectContainer.appendChild(el);
}

Game.getOverlayCanvas = function() {
	return swarmCanvas;
}

Game.getTargets = function() {
	return targets;
}

Game.getAntiTargets = function() {
	return antiTargets;
}

Game.getMapWidth = function() {
	return mapWidth;
}

Game.getMapHeight = function() {
	return mapHeight;
}

Game.getMapBlocks = function() {
	return blockBoxes;
}

Game.getTriggers = function() {
	return triggers;
}

Game.isAlive = function() {
	return !dead;
}

var Pi2 = 2*Math.PI;

Game.closestAngle = function(orgAngle, newAngle) {
	if (orgAngle < -Math.PI) orgAngle += Pi2;
	if (orgAngle >= Math.PI) orgAngle -= Pi2;
	if (newAngle < -Math.PI) newAngle += Pi2;
	if (newAngle >= Math.PI) newAngle -= Pi2;
	var dif1 = newAngle - orgAngle;
	var dif2 = newAngle + Math.PI*2 - orgAngle;
	var dif3 = newAngle - Math.PI*2 - orgAngle;

	if (dif1 < 0) dif1 = -dif1;
	if (dif2 < 0) dif2 = -dif2;
	if (dif3 < 0) dif3 = -dif3;

	if (dif2 < dif1 && dif2 < dif3) {		// dif 2 smallest
		newAngle += Pi2;
	} else if (dif3 < dif1 && dif3 < dif2) {	// dif 3 smallest
		newAngle -= Pi2;
	}
	return newAngle;
}

Game.killHero = killHero;

Game.spawnExplosion = spawnExplosion;

Game.spawnPointText = spawnPointText;

Game.spawnText = spawnText;

Game.isPointInBlock = isPointInBlock;

Game.init = init;

})();

(function() {

Game.Sound = {};

var soundLoaded = false;
var sounds = [];

//soundManager.waitForWindowLoad = true;
soundManager.allowPolling = true;
soundManager.debugMode = true;

activeSounds = [];
var musicCurrent = "";
var music = [];
var musicEnabled = true;
var musicPaused = false;

soundManager.onload = function() {
	soundLoaded = true;
}

function playMusic(file, start) {
	start = start || 0;

	if (!soundLoaded) return;

	if (musicCurrent) {
		soundManager.stop(musicCurrent);
	}
	if (!music[file]) {
		music[file] = soundManager.createSound(
			{id:file, url:"music/"+file+".mp3"}
		);
	}
	if (musicEnabled) {
		soundManager.play(file, {volume:50, position:start*1000});
		soundManager.setPosition(file, start*1000);
	}
	musicCurrent = file;
}

function stopMusic() {
	if (!soundLoaded) return;
	if (musicCurrent) {
		soundManager.stop(musicCurrent);
	}
}

function pauseMusic() {
	if (!soundLoaded) return;

	if (musicCurrent) {
		if (musicEnabled)
			soundManager.togglePause(musicCurrent);
		musicPaused = true;
	}
}

function unpauseMusic() {
	if (!soundLoaded) return;

	if (musicCurrent) {
		if (musicEnabled)
			soundManager.togglePause(musicCurrent);
		musicPaused = false;
	}
}

Game.Sound.pauseMusic = pauseMusic;
Game.Sound.unpauseMusic = unpauseMusic;
Game.Sound.playMusic = playMusic;
Game.Sound.stopMusic = stopMusic;

Game.Sound.playSound = function() {};
Game.Sound.stopSound = function() {};


Game.Sound.isMusicEnabled = function() {
	return musicEnabled;
}
Game.Sound.setMusicEnabled = function(value) {
	musicEnabled = value;
}

Game.Sound.getCurrentMusic = function() {
	return musicCurrent;
}
Game.Sound.setCurrentMusic = function(music) {
	musicCurrent = music;
}


})();Game.MapObjects.Attractor = function() {

	return {

		x : 0,
		y : 0,
		range : 128,

		rangeColor : "#bff1b8",
		direction : "",

		draw : function() {

			var canvas = document.createElement("canvas");
			canvas.width = canvas.height = 32;

			canvas.style.width = canvas.style.height = "32px";
			canvas.style.position = "absolute";
			canvas.style.left = this.x - 16 - 1;
			canvas.style.top = this.y - 16 - 1;

			var ctx = canvas.getContext("2d");

			ctx.beginPath();
			ctx.arc(16, 16, 10, Math.PI*2, 0, true);
			ctx.closePath();

			ctx.fillStyle = "rgba(170,255,180,0.6)";
			ctx.fill();

			ctx.lineWidth = 12;
			ctx.strokeStyle = "rgba(150,250,150,0.1)";
			ctx.stroke();

			ctx.lineWidth = 11;
			ctx.strokeStyle = "rgba(150,250,150,0.1)";
			ctx.stroke();

			ctx.lineWidth = 5;
			ctx.strokeStyle = "rgba(250,250,250,0.4)";
			ctx.stroke();

			ctx.lineWidth = 3;
			ctx.strokeStyle = "rgba(250,250,250,0.7)";
			ctx.stroke();

			ctx.beginPath();
			ctx.arc(16, 16, 15, Math.PI*2, 0, true);
			ctx.closePath();

			ctx.lineWidth = 2;
			ctx.strokeStyle = "rgba(200,250,200,0.4)";
			ctx.stroke();

			Game.addMapObjectElement(canvas);
		},

		getTriggers : function() {
			return [
				{
					x : this.x,
					y : this.y,
					range : this.range,
					action : swarmMemberAttracted,
					mapObject : this
				}
			];
		},

		clear : function() {
		}
	}
}

function swarmMemberAttracted(swarm, i, attractor) {
	var member = swarm[i];
	if (member.attractorEffect) {
		var dX = attractor.x - member.x;
		var dY = attractor.y - member.y;
		var dist = Math.sqrt(dX*dX + dY*dY);

		var attAngle = Game.closestAngle(member.angle, Math.atan2(-dY, dX));

		var strength = dist / attractor.range * member.attractorEffect;

		member.angle = (member.angle + attAngle * strength) / (1+strength);
		member.speed *= 1.1;
	}
}Game.MapObjects.Director = function() {

	return {

		x : 0,
		y : 0,
		range : 128,

		rangeColor : "#88fbff",
		direction : "",

		draw : function() {
			var canvas = document.createElement("canvas");
			canvas.width = canvas.height = 64;

			canvas.style.width = canvas.style.height = "64px";
			canvas.style.position = "absolute";
			canvas.style.left = this.x - 32 - 1;
			canvas.style.top = this.y - 32 - 1;

			var ctx = canvas.getContext("2d");


			ctx.translate(32,32);
			switch (this.direction) {
				case "up" :
					//ctx.rotate(0);
					break;
				case "right" :
					ctx.rotate(Math.PI*0.5);
					break;
				case "down" :
					ctx.rotate(Math.PI);
					break;
				case "left" :
					ctx.rotate(-Math.PI*0.5);
					break;
			}
			ctx.translate(-32,-32);

			ctx.beginPath();


			ctx.moveTo(50, 28);
			ctx.lineTo(50, 60);
			ctx.lineTo(14, 60);
			ctx.lineTo(14, 28);

			ctx.lineTo(4, 28);
			ctx.lineTo(32, 8);
			ctx.lineTo(60, 28);
			ctx.lineTo(60, 28);

			ctx.closePath();

			ctx.fillStyle = "rgba(80,120,200,0.6)";
			ctx.fill();

			ctx.lineWidth = 2;
			ctx.strokeStyle = "rgba(80,120,200,0.5)";
			ctx.stroke();



			Game.addMapObjectElement(canvas);

		},

		getTriggers : function() {
			return [
				{
					x : this.x,
					y : this.y,
					range : this.range,
					action : swarmMemberDirected,
					mapObject : this
				}
			];
		},

		clear : function() {
		}
	}
}

function swarmMemberDirected(swarm, i, director) {
	var member = swarm[i];

	if (member.directorEffect) {
		var directorX = 0;
		var directorY = 0;
		switch (director.direction) {
			case "left" :
				directorX = -1; break;
			case "right" :
				directorX = 1; break;
			case "up" :
				directorY = -1; break;
			case "down" :
				directorY = 1; break;
		}


		var dX = director.x - member.x;
		var dY = director.y - member.y;
		var dist = Math.sqrt(dX*dX + dY*dY);

		var effAngle = Game.closestAngle(member.angle, Math.atan2(-directorY, directorX));

		var strength = Math.sqrt(dist / director.range) * member.directorEffect;

		member.angle = (member.angle + effAngle*strength) / (1+strength);

		member.speed *= 1.2;
	}

}(function() {

Game.MapObjects.Repulsor = function() {

	return {

		x : 0,
		y : 0,
		range : 128,

		rangeColor : "#bff1b8",
		direction : "",

		draw : function() {

			var canvas = document.createElement("canvas");
			canvas.width = canvas.height = 32;

			canvas.style.width = canvas.style.height = "32px";
			canvas.style.position = "absolute";
			canvas.style.left = this.x - 16 - 1;
			canvas.style.top = this.y - 16 - 1;

			var ctx = canvas.getContext("2d");

			ctx.beginPath();
			ctx.arc(16, 16, 10, Math.PI*2, 0, true);
			ctx.closePath();

			ctx.fillStyle = "rgba(255,140,140,0.7)";
			ctx.fill();

			ctx.lineWidth = 12;
			ctx.strokeStyle = "rgba(250,150,150,0.1)";
			ctx.stroke();

			ctx.lineWidth = 11;
			ctx.strokeStyle = "rgba(250,150,150,0.1)";
			ctx.stroke();

			ctx.lineWidth = 5;
			ctx.strokeStyle = "rgba(250,250,250,0.4)";
			ctx.stroke();

			ctx.lineWidth = 3;
			ctx.strokeStyle = "rgba(250,250,250,0.7)";
			ctx.stroke();

			ctx.beginPath();
			ctx.arc(16, 16, 15, Math.PI*2, 0, true);
			ctx.closePath();

			ctx.lineWidth = 2;
			ctx.strokeStyle = "rgba(250,200,200,0.4)";
			ctx.stroke();

			Game.addMapObjectElement(canvas);
		},

		getTriggers : function() {
			return [
				{
					x : this.x,
					y : this.y,
					range : this.range,
					action : swarmMemberRepulsed,
					mapObject : this
				}
			];
		},

		clear : function() {
		}
	}
}

function swarmMemberRepulsed(swarm, i, repulsor) {
	var member = swarm[i];
	if (member.repulsorEffect) {
		var dX = repulsor.x - member.x;
		var dY = repulsor.y - member.y;
		var dist = Math.sqrt(dX*dX + dY*dY);

		var defAngle = Game.closestAngle(member.angle, Math.atan2(dY, -dX));

		var strength = dist / repulsor.range * member.repulsorEffect;

		member.angle = (member.angle + defAngle * strength) / (1+strength);
		member.speed *= 1.1;
	}
}

})();
(function() {

Game.MapObjects.Vortex = function() {

	return {

		x : 0,
		y : 0,
		range : 128,

		rangeColor : "#22e1e8",
		direction : "",

		vortexRange : 16,
		deflectRange : 128,
		gravityRange : 64,

		gravityStrength : 24,

		interval : 0,

		draw : function() {

			var img = new Image();
			img.src = "sprites/vortex_underlay.png";
			img.style.position = "absolute";
			img.style.left = this.x - 48 - 1;
			img.style.top = this.y - 48 - 1;
			//img.style.opacity = 0.7;
			Game.addMapObjectElement(img);

			var overImg = new Image();
			var me = this;
			overImg.onload = function() {
				var canvas = document.createElement("canvas")
				canvas.width = 96;
				canvas.height = 96;
				canvas.style.position = "absolute";
				canvas.style.width = 96;
				canvas.style.height = 96;
				canvas.style.left = me.x - 48 - 1;
				canvas.style.top = me.y - 48 - 1;
		
				var angle = 0;
		
				this.interval = setInterval(
					function() {
						angle += 2;
						if (angle > 360) angle -= 360;

						var ctx = canvas.getContext("2d");
						canvas.width=canvas.width;
						ctx.save();
						ctx.translate(96/2,96/2)
						ctx.rotate(angle * Math.PI / 180);
						ctx.translate(-96/2,-96/2)
						ctx.drawImage(overImg,0,0);
						ctx.restore();
					}, 1000 / 8
				);
				Game.addMapObjectElement(canvas);
			}
			overImg.src = "sprites/vortex_overlay.png";

		},

		getTriggers : function() {
			return [
				{
					x : this.x,
					y : this.y,
					range : this.vortexRange,
					action : swarmMemberInVortex,
					mapObject : this
				},
				{
					x : this.x,
					y : this.y,
					range : this.deflectRange,
					action : swarmMemberInDeflectRange,
					mapObject : this
				},
				{
					x : this.x,
					y : this.y,
					range : this.gravityRange,
					action : swarmMemberInGravityWell,
					mapObject : this
				}

			];
		},

		clear : function() {
			clearInterval(this.interval);
		}
	}
}

function swarmMemberInDeflectRange(swarm, i, vortex) {
	var member = swarm[i];
	if (member.canBeVortexed) {
		var dX = vortex.x - member.x;
		var dY = vortex.y - member.y;
		var dist = Math.sqrt(dX*dX + dY*dY);
		var defAngle = Game.closestAngle(member.angle, Math.atan2(dY, -dX));
		var strength = dist / vortex.deflectRange * 0.02;
		member.angle = (member.angle + defAngle * strength) / (1+strength);
		member.speed *= 1.1;
	}
}

function swarmMemberInVortex(swarm, i, vortex) {
	var member = swarm[i];
	if (member.canBeVortexed) {
		Game.killHero(i, true);
	}
}

function swarmMemberInGravityWell(swarm, i, vortex) {
	var member = swarm[i];
	if (member.canBeVortexed) {
		var dX = vortex.x - member.x;
		var dY = vortex.y - member.y;
	
		var dist = Math.sqrt(dX*dX + dY*dY);
		if (dist < vortex.gravityRange) {
			dX /= dist;
			dY /= dist;
			var strength = Math.sqrt(1 - dist / vortex.gravityRange);
			member.x += dX * strength * vortex.gravityStrength;
			member.y += dY * strength * vortex.gravityStrength;
		}
	}
}

})();(function() {

Game.MapObjects.Zapper = function() {

	return {

		x : 0,
		y : 0,
		range : 128,

		rangeColor : "#92bcdd",

		overloaded : false,
		lastZapTime : 0,
		minZapDelta : 800,

		interval : 0,

		draw : function() {

			var numImages = 4;

			var images = [];

			for (var i=0;i<numImages;i++) {
				var canvas = document.createElement("canvas");
				canvas.width = canvas.height = 64;
				canvas.style.width = canvas.style.height = "64px";

				var ctx = canvas.getContext("2d");

				ctx.beginPath();
				ctx.arc(32, 32, 24, Math.PI*2, 0, true);
				ctx.closePath();

				ctx.fillStyle = "rgba(100,160,170,0.7)";
				ctx.fill();

				ctx.lineWidth = 3;
				ctx.strokeStyle = "rgba(240,250,255,0.9)";
				ctx.stroke();

				ctx.beginPath();
				ctx.moveTo(34,16);
				ctx.lineTo(21,32);
				ctx.lineTo(32,36);
				ctx.lineTo(28,48);
				ctx.lineTo(42,32);
				ctx.lineTo(32,28);
				ctx.lineTo(34,16);
				ctx.closePath();

				ctx.strokeStyle = "rgba(250,230,110,0.7)";
				ctx.lineWidth = 2;
				ctx.stroke();

				var alpha = 0;
				switch (i) {
					case 0 : alpha = 1; break;
					case 1 : alpha = 0.7; break;
					case 2 : alpha = 0.5; break;
					case 3 : alpha = 0.7; break;
				}

				ctx.fillStyle = "rgba(250,230,110,"+alpha+")";
				ctx.fill();

				canvas.style.position = "absolute";
				canvas.style.display = "none";
				canvas.style.left = this.x - 32 - 1;
				canvas.style.top = this.y - 32 - 1;


				Game.addMapObjectElement(canvas);

				images.push(canvas);
			}
			
			var animStep = 0;
			this.interval = setInterval(
				function() {
					animStep++;
					if (animStep >= images.length)
						animStep = 0;
		
					for (var i=0;i<images.length;i++) {
						if (i==animStep) {
							images[i].style.display = "block";
						} else {
							images[i].style.display = "none";
						}
					}
				}, 200
			);

		},

		getTriggers : function() {
			return [
				{
					x : this.x,
					y : this.y,
					range : this.range,
					action : swarmMemberZapped,
					mapObject : this
				}
			];
		},

		clear : function() {
			clearInterval(this.interval);
		}
	}
}

function swarmMemberZapped(swarm, i, zapper) {
	var member = swarm[i];
	if (!member.canBeZapped) return;

	var time = new Date().getTime();
	var delta = time - zapper.lastZapTime;
	if (delta > zapper.minZapDelta) {

		var dX = member.x - zapper.x;
		var dY = member.y - zapper.y;
		var slope = dY/dX;

		var ctx = Game.getOverlayCanvas().getContext("2d");

		var lines = [
			[10, "rgba(180,200,255,0.2)"],
			[6, "rgba(180,200,255,0.3)"],
			[1, "rgb(255,255,225)"],
			[0.5, "rgb(255,255,225)"],
			[0.5, "rgb(255,255,225)"],
			[0.5, "rgb(255,255,225)"]
		];

		for (var l=0;l<lines.length;l++) {
			ctx.lineWidth = lines[l][0];
			ctx.strokeStyle = lines[l][1];
			ctx.beginPath();
			ctx.moveTo(zapper.x, zapper.y);
			for (var a=0;a<4;a++) {
				var stepX = dX / 4 * a;
				var x = zapper.x + stepX;
				var y = zapper.y + slope * stepX;
				x += (Math.random()-0.5) * 10;
				y += (Math.random()-0.5) * 10;
				ctx.lineTo(x, y);
			}
			ctx.lineTo(member.x, member.y);
			ctx.stroke();
		}
		Game.spawnText(member.x, member.y, "zap!");
		Game.killHero(i, true);
		zapper.lastZapTime = time;
	}
}

})();
(function() {

Game.SwarmTypes = {};

Game.Swarm = {}


Game.Swarm.initSwarm = function(swarm, x, y, angle, num, swarmType)
{
	for (var i=0;i<num;i++) {
		swarm.push(Game.Swarm.createSwarmMember(x, y, angle, swarmType));
	}
}

Game.Swarm.createSwarmMember = function(x, y, angle, swarmType)
{
	var fDX = Math.random();
	var fDY = Math.random();
	var fD = Math.sqrt(fDX*fDX+fDY*fDY);

	var member = swarmType();

	var ox = x;
	var oy = y;
	x = ox + (Math.random()-0.5) * member.reach;
	y = oy + (Math.random()-0.5) * member.reach;

	while (Game.isPointInBlock(x, y, member.collisionRadius)) {
		x = ox + (Math.random()-0.5) * member.reach;
		y = oy + (Math.random()-0.5) * 50;
	}

	member.x = x;
	member.y = y;
	member.angle = angle;

	member.reach += (Math.random()-0.5) * 20;
	member.crowdLimit += (Math.random()-0.5) * 10;
	member.superCrowdLimit += (Math.random()-0.5) * 10;

	member.speed = member.baseSpeed;

	member.posHistory = [];

	return member;
}

Game.Swarm.removeSwarmMember = function(swarm, idx) {
	swarm[idx].removed = true;
}

function getDistToMember(member1, member2) {
	var dist = {
		deltaX : 0,
		deltaY : 0,
		dist : 0
	};

	var deltaX = member2.x - member1.x;
	var deltaY = member2.y - member1.y;

	var fX = member2.x;
	var fY = member2.y;

	dist.x = fX;
	dist.y = fY;
	dist.deltaX = deltaX;
	dist.deltaY = deltaY;
	dist.dist = Math.sqrt(dist.deltaX*dist.deltaX + dist.deltaY*dist.deltaY);

	return dist;
}

Game.Swarm.moveSwarm = function(swarm) {
	for (var i=0;i<swarm.length;i++) {
		swarm[i].posHistory.push([swarm[i].x, swarm[i].y]);
	}


	var mapWidth = Game.getMapWidth();
	var mapHeight = Game.getMapHeight();
	var blockBoxes = Game.getMapBlocks();

	for (var i=0;i<swarm.length;i++) {
		var member = swarm[i];

		var reach = member.reach;
		var crowdLimit = member.crowdLimit;
		var superCrowdLimit = member.superCrowdLimit;

		var avgX = member.x;
		var avgY = member.y;

		var avgAngle = 0;

		var angle = member.angle;

		var reachMembers = 0;
		var crowdMembers = 0;

		var crowdAngle = 0;
		var crowdAngleWeight = 0;

		var curAngle = member.angle;

		var avgSpeed = 0;


		for (var a=0;a<swarm.length;a++) {
			var otherMember = swarm[a];
			if (a!=i) {
				var dist = getDistToMember(member, otherMember);

				var dX = dist.deltaX;
				var dY = dist.deltaY;
				var dist = dist.dist;

				if (dist < reach) {

					if (dist < crowdLimit && dist != 0) {
						if (dist != 0) {
							var w = (dist < superCrowdLimit ? 20 : 1);

							var angleAway = Game.closestAngle(angle, Math.atan2(dY, -dX));
							crowdAngle += angleAway;
							crowdAngleWeight += w;
						}
					} else {
						avgX += dist.x;
						avgY += dist.y;
					}

					reachMembers++;
					avgSpeed += otherMember.speed;

					avgAngle += otherMember.angle;
				}
			}

		}

		if (reachMembers > 0) {

			var cohAngle = angle;
			var sepAngle = angle;
			var alignAngle = angle;

			avgX /= reachMembers+1;	// average location of local group
			avgY /= reachMembers+1;
			var avg = getDistToMember(member, {x : avgX, y : avgY});
			var dAvgX = (avgX - member.x);
			var dAvgY = (avgY - member.y);
			var dAvgDist = dAvgX*dAvgX + dAvgY*dAvgY; // distance^2 to average location of local group
			if (dAvgDist > 0) {
				cohAngle = Math.atan2(-dAvgY, dAvgX);
			}

			// avoid crowdedness
			if (crowdAngleWeight > 0) {
				sepAngle = crowdAngle / crowdAngleWeight;
			}

			// alignment, move in the same direction!
			avgAngle /= reachMembers;


			cohAngle = Game.closestAngle(angle, cohAngle);
			sepAngle = Game.closestAngle(angle, sepAngle);
			avgAngle = Game.closestAngle(angle, avgAngle);

			angle = (angle 
				+ cohAngle * member.cohesionWeight 
				+ avgAngle * member.alignmentWeight
				+ sepAngle * member.separationWeight)
				/ (1 + member.cohesionWeight + member.alignmentWeight + member.separationWeight);

			member.speed = (member.speed + avgSpeed/reachMembers) / 2;
		}

		// ok, now the member has changed its direction according to swarming rules

		if (member.targetEffect > 0) {
			// turn towards the nearest target point
			var targets = Game.getTargets();
			if (targets.length > 0) {
				var target = targets[targets.length-1];
				var dX = target.x - member.x;
				var dY = target.y - member.y;
				var dist = dX*dX + dY*dY;
				var targetAngle = Game.closestAngle(angle, Math.atan2(-dY, dX));
				angle = (angle + targetAngle * member.targetEffect) / (1 + member.targetEffect);
			}
	
			// turn away from any antitarget points
			var antiTargets = Game.getAntiTargets();
			if (antiTargets.length > 0) {
				var antiTarget = antiTargets[antiTargets.length-1];
				var dX = antiTarget.x - member.x;
				var dY = antiTarget.y - member.y;
				var dist = dX*dX + dY*dY;
				var targetAngle = Game.closestAngle(angle, Math.atan2(dY, -dX));
				angle = (angle + targetAngle * member.targetEffect) / (1 + member.targetEffect);
			}

			if (targets.length > 0 || antiTargets.length > 0) {
				member.speed *= 1.3;
			} else {
				member.speed /= 1.05;
			}
		}

		if (member.customMovement) {
			var customMove = member.customMovement(swarm, i, angle, mapWidth, mapHeight);
			if (customMove && typeof customMove.angle != "undefined") {
				angle = customMove.angle;
			}
		}

		// triggers
		member.angle = angle; // temporarily pretend that the current calculated angle is final
		var triggers = Game.getTriggers();
		for (var a=0;a<triggers.length;a++) {
			var trigger = triggers[a];
			var dX = trigger.x - member.x;
			var dY = trigger.y - member.y;
			var dist = dX*dX + dY*dY;
			if (dist < trigger.range*trigger.range) {
				trigger.action(swarm, i, trigger.mapObject);
			}
		}
		angle = member.angle;

		if (member.speed > member.maxSpeed) member.speed = member.maxSpeed;
		if (member.speed < member.minSpeed) member.speed = member.minSpeed;

		// clamp new direction to be within the maxTheta range that this member can turn per frame
		var newAngle = Game.closestAngle(curAngle, angle);
		if (Math.abs(newAngle - curAngle) > member.maxTheta) {
			newAngle = curAngle + member.maxTheta * (newAngle < curAngle ? -1 : 1);
		}

		member.angle = newAngle;


		var dirX = Math.cos(member.angle);
		var dirY = -Math.sin(member.angle);

		// new position
		var newX = member.x + dirX * member.speed;
		var newY = member.y + dirY * member.speed;

		var boundCollide = true;
		if (member.boundaryCheck) {
			boundCollide = member.boundaryCheck(swarm, i, mapWidth, mapHeight);
		}

		var colRadius = member.collisionRadius;

		oldX = member.x;
		oldY = member.y;

		if (boundCollide) {
			var maxY = mapHeight - 32;
			var minY = 32;
			var maxX = mapWidth;
			var minX = 0;
			// collision against map boundaries

			if (oldY + colRadius > maxY && dirY > 0) {
				newY = maxY - colRadius;
				dirY *= -1;
			}
			if (oldY - colRadius < minY && dirY < 0) {
				newY = minY + colRadius;
				dirY *= -1;
			}
			if (oldX + colRadius > maxX && dirX > 0) {
				newX = maxX - colRadius;
				dirX *= -1;
			}
			if (oldX - colRadius < minX && dirX < 0) {
				newX = minX + colRadius;
				dirX *= -1;
			}
		}

		// collision detection against map boxes


		var dX = newX - oldX;
		var dY = newY - oldY;

		var slope = dY / dX;
		var slope2 = dX / dY;

		var intY = -slope * oldX + oldY; // intersection with y axis
		var intX = -intY / slope;
		//var intX = -slope * oldY + oldX; // intersection with x axis

		var colRadius2 = colRadius*colRadius;

		var moveLeft = dX < 0;
		var moveDown = dY > 0;

		for (var a=0;a<blockBoxes.length;a++) {
			var box = blockBoxes[a];

			var boxX1 = box[0];
			var boxX2 = box[0]+box[2];
			var boxY1 = box[1];
			var boxY2 = box[1]+box[3];

			var collide = false;

			// left side
			if (!moveLeft && boxX1 - oldX > colRadius && !collide) {
				if (boxX1 - newX < colRadius && newX < boxX2) {
					if (newY >= boxY1 && newY < boxY2) {
						newX = boxX1-colRadius-1;
						newY = newX * slope + intY;
						if (dirX > 0) dirX = -dirX;
						collide = true;
					}
				}
			}
			// right
			if (moveLeft && oldX - boxX2 > colRadius && !collide) {
				if (newX - boxX2 < colRadius && newX > boxX1) {
					if (newY >= boxY1 && newY < boxY2) {
						newX = boxX2+colRadius+1;
						newY = newX * slope + intY;
						if (dirX < 0) dirX = -dirX;
						collide = true;
					}
				}
			}
			// bottom
			if (!moveDown && oldY - boxY2 > colRadius && !collide) {
				if (newY - boxY2 < colRadius && newY > boxY1) {
					if (newX >= boxX1 && newX < boxX2) {
						newY = boxY2+colRadius+1;
						newX = newY * slope2 + intX;
						if (dirY < 0) dirY = -dirY;
						collide = true;
					}
				}
			}
			// top
			if (moveDown && boxY1 - oldY > colRadius && !collide) {
				if (boxY1 - newY < colRadius && newY < boxY2) {
					if (newX >= boxX1 && newX < boxX2) {
						newY = boxY1-colRadius-1;
						newX = newY * slope2 + intX;
						if (dirY > 0) dirY = -dirY;
						collide = true;
					}
				}
			}

			// ok, we didn't collide with the sides
			if (!collide) {
				// now check the corners

				// this corner checking should be moved to after _all_ boxes have had their sides checked,
				// in case we have two adjacent boxes, we don't want their corners to matter!

				// top left
				if (!moveLeft) {
					var dX = boxX1 - newX;
					var dY = boxY1 - newY;
					var dist = dX*dX + dY*dY;
					if (dist < colRadius2) {
						// ok, distance to the corner is less than radius
						// we should the correct angle, but for now we just turn it around
						if (dirX > 0) dirX = -dirX;
						if (dirY > 0) dirY = -dirY;
					}
				}
				// top right
				if (moveLeft) {
					var dX = boxX2 - newX;
					var dY = boxY1 - newY;
					var dist = dX*dX + dY*dY;
					if (dist < colRadius2) {
						if (dirX < 0) dirX = -dirX;
						if (dirY > 0) dirY = -dirY;
					}
				}

				// bottom left
				if (!moveLeft) {
					var dX = boxX1 - newX;
					var dY = boxY2 - newY;
					var dist = dX*dX + dY*dY;
					if (dist < colRadius2) {
						// ok, distance to the corner is less than radius
						// we should the correct angle, but for now we just turn it around
						if (dirX > 0) dirX = -dirX;
						if (dirY < 0) dirY = -dirY;
					}
				}
				// bottom right
				if (moveLeft) {
					var dX = boxX2 - newX;
					var dY = boxY2 - newY;
					var dist = dX*dX + dY*dY;
					if (dist < colRadius2) {
						if (dirX < 0) dirX = -dirX;
						if (dirY < 0) dirY = -dirY;
					}
				}
			}
		}

		member.angle = Math.atan2(-dirY, dirX);

		if (!Game.isPointInBlock(newX, newY, member.collisionRadius)) {
			member.x = newX;
			member.y = newY;
		}

		if (member.posHistory.length > 1) {
			var stuck = true;
			var sX = newX|0;
			var sY = newY|0;

			var hisPos = member.posHistory[member.posHistory.length-1];

			if ((hisPos[0]|0) != sX || (hisPos[1]|0) != sY) {
				stuck = false;
			}
			if (stuck) {
				member.angle = Math.random()*Math.PI*2 - Math.PI;
			}
		}
	}

	// remove members marked for removal
	var newSwarm = [];
	for (var i=0;i<swarm.length;i++) {
		if (!swarm[i].removed) {
			newSwarm.push(swarm[i]);
		}
	}

	newSwarm.points = swarm.points;

	return newSwarm;
}



Game.Swarm.renderSwarm = function(swarm, ctx, trailCtx) {

	for (var i=0;i<swarm.length;i++) {
		var member = swarm[i];

		var x = Math.round(member.x);
		var y = Math.round(member.y);

		member.draw(ctx);

		if (member.drawTrail) {
			member.drawTrail(trailCtx);
		}

	}

}

})();(function() {

Game.SwarmTypes.Hero = function () {

	return {

		reach : 150,
		crowdLimit : 30,
		superCrowdLimit : 20,
	
		cohesionWeight : 10,
		alignmentWeight : 5, 
		separationWeight : 10,

		baseSpeed : 2,
		minSpeed : 3,
		maxSpeed : 15,
	
		maxTheta : 0.30 * Math.PI,

		attractorEffect : 1,
		repulsorEffect : 1,
		directorEffect : 1.5,
		vortexEffect : 1,
		canBeZapped : true,
		canBeVortexed : true,

		targetEffect : 1,

		collisionRadius : 2,

		customMovement : function(swarm, i, angle, mapWidth, mapHeight) {
			return {
				angle : angle
			};
		},

		explode : function(x, y) {
			Game.spawnExplosion(x, y, [100,220,255], 3, 30, 15, 0.3, 0.2);
			Game.spawnExplosion(x, y, [200,255,255], 2, 20, 15, 0.1, 0.2);
		},

		draw : function(ctx) {
			var size = 6;

			ctx.fillStyle = "#EEEEFF";
			ctx.lineWidth = 3;
			ctx.strokeStyle = "#6666AA";

			ctx.strokeRect(
				((this.x - size/2)|0)+0.5, 
				((this.y - size/2)|0)+0.5, 
				size-1, size-1
			);
			ctx.fillRect(
				((this.x - size/2)|0)+1, 
				((this.y - size/2)|0)+1, 
				size-2, size-2
			);

		},

		drawTrail : function(ctx) {
			var hist = this.posHistory;
			var size = 2.5;
			for (var i=hist.length-1;i>=0&&i>hist.length-6;i--) {
				ctx.fillRect(
					(hist[i][0] - size/2)|0, 
					(hist[i][1] - size/2)|0, 
					size|0, size|0
				);
				size *= 0.8;
			}
		}

	}
}

})();
(function() {
var antiHeroCount = 0;

Game.SwarmTypes.Antihero = function () {

	antiHeroCount++;

	return {

		reach : 120,
		crowdLimit : 30,
		superCrowdLimit : 20,
	
		cohesionWeight : 5,
		alignmentWeight : 5, 
		separationWeight : 10,

		baseSpeed : 2,
		minSpeed : 14,
		maxSpeed : 14,
	
		maxTheta : 0.25 * Math.PI,

		attractorEffect : 1.5,
		repulsorEffect : 1,
		directorEffect : 2,
		vortexEffect : 1,

		targetEffect : -1,

		collisionRadius : 5,

		health : 50,

		idx : antiHeroCount,

		customMovement : function(swarm, i, angle, mapWidth, mapHeight) {
			if (Game.isAlive()) {
				var me = swarm[i];
				var heroPos = Game.getHeroCenter();
				var dX = heroPos.x - me.x;
				var dY = heroPos.y - me.y;

				var targetAngle = Game.closestAngle(angle, Math.atan2(-dY, dX));

				var strength = 0.8;
				var angleNew = (angle + targetAngle * strength) / (1+strength);
	
				return {
					angle : angleNew
				};
			}
		},

		explode : function(x, y) {
			Game.spawnExplosion(x, y, [255,130,80], 3, 30, 15, 0.3, 0.2);
			Game.spawnExplosion(x, y, [255,180,130], 2, 20, 15, 0.1, 0.2);
		},

		draw : function(ctx) {
			var size = 6;

			ctx.fillStyle = "#ffcccc";
			ctx.lineWidth = 3;
			ctx.strokeStyle = "#cc5555";
			ctx.strokeRect(
				((this.x - size/2)|0)+0.5, 
				((this.y - size/2)|0)+0.5, 
				size-1, size-1
			);
			ctx.fillRect(
				((this.x - size/2)|0)+1, 
				((this.y - size/2)|0)+1, 
				size-2, size-2
			);
		},

		drawTrail : function(ctx) {
			var hist = this.posHistory;
			var size = 2.5;
			for (var i=hist.length-1;i>=0&&i>hist.length-6;i--) {
				ctx.fillRect(
					(hist[i][0] - size/2)|0, 
					(hist[i][1] - size/2)|0, 
					size|0, size|0
				);
				size *= 0.8;
			}
		}

	}
}

})();

(function() {


var animStep = 0;
setInterval(
	function() {
		animStep++;
		if (animStep >= 4)
			animStep = 0;
	}, 200
);


Game.SwarmTypes.Biggie = function () {

	return {
		reach : 100,
		crowdLimit : 100,
		superCrowdLimit : 70,

		cohesionWeight : 0,
		alignmentWeight : 0, 
		separationWeight : 2,

		baseSpeed : 8,
		minSpeed : 8,
		maxSpeed : 8,

		maxTheta : 0.10 * Math.PI,

		attractorEffect : 0,
		repulsorEffect : 0,
		vortexEffect : 0,
		targetEffect : 0,
		collisionRadius : 27,
		killRadius : 16,

		health : 700,
		
		customMovement : function() {
			this.speed = animStep * 3;
		},

		explode : function(x, y) {
			Game.spawnExplosion(x, y, [255,180,120], 4, 40, 20, 0.3, 1);
			Game.spawnExplosion(x, y, [255,230,180], 3, 30, 20, 0.1, 0.2);
		},

		draw : function(oCtx) {
			var iSize = 8;

			if (!Game.SwarmTypes.Biggie.protoImage) {
				Game.SwarmTypes.Biggie.protoImage = [];
				var angleCut = 0.499;
				for (var i=0;i<4;i++) {
					var canvas = document.createElement("canvas");
					canvas.width = canvas.height = 64;
					var ctx = canvas.getContext("2d");
	

					ctx.beginPath();
	
					ctx.moveTo(32,32);
					ctx.arc(32, 32, 24, Math.PI*2 * angleCut, Math.PI*2 - Math.PI * 2 * angleCut, true);

					if (i < 2)
						angleCut -= 0.07;
					else
						angleCut += 0.07;

					ctx.closePath();
	
					ctx.lineWidth = 16;
					ctx.strokeStyle = "rgba(255,0,0, 0.2)";
					ctx.stroke();
	
					ctx.lineWidth = 12;
					ctx.stroke();
	
					ctx.lineWidth = 8;
					ctx.stroke();
	
					ctx.lineWidth = 4;
					ctx.stroke();
	
					ctx.fillStyle = "rgba(255,0,0,0.5)";
					ctx.fill();
	
					ctx.strokeStyle = "rgb(255,200,200)";
					ctx.lineWidth = 2;
					ctx.stroke();
	
	
					Game.SwarmTypes.Biggie.protoImage.push(canvas);
				}
			}

			var img = Game.SwarmTypes.Biggie.protoImage[animStep];
	
			oCtx.save();
			oCtx.translate(this.x, this.y);

			oCtx.scale(-1,1);
			oCtx.rotate(this.angle);

			oCtx.drawImage(img, -32, -32);
			oCtx.translate(-this.x, -this.y);
			oCtx.restore();
	
		},

		drawTrail : function(oCtx) {
			//oCtx.fillRect(this.x-1,this.y-1,2,2);
		}
	}
}

})();

(function() {

Game.SwarmTypes.Predator = function () {

	return {
		reach : 100,
		crowdLimit : 70,
		superCrowdLimit : 50,

		cohesionWeight : 0.2,
		alignmentWeight : 3, 
		separationWeight : 3,

		baseSpeed : 10,
		minSpeed : 8,
		maxSpeed : 12,

		maxTheta : 0.10 * Math.PI,

		attractorEffect : 0,
		repulsorEffect : 0,
		vortexEffect : 0,
		targetEffect : -10,

		collisionRadius : 20,
		killRadius : 16,

		health : 250,

		animStep : 0,

		customMovement : function(swarm, i, angle, mapWidth, mapHeight) {
			if (Game.isAlive()) {
				var me = swarm[i];
				var heroPos = Game.getHeroCenter();
				var dX = heroPos.x - me.x;
				var dY = heroPos.y - me.y;

				var targetAngle = Game.closestAngle(angle, Math.atan2(-dY, dX));

				var strength = 0.5;
				var angleNew = (angle + targetAngle * strength) / (1+strength);
	
				return {
					angle : angleNew
				};
			}
		},

		explode : function(x, y) {
			Game.spawnExplosion(x, y, [255,180,120], 4, 40, 20, 0.3, 1.5);
			Game.spawnExplosion(x, y, [255,230,180], 3, 30, 20, 0.1, 0.2);
		},

		draw : function(oCtx) {
			var iSize = 8;

			if (!Game.SwarmTypes.Predator.protoImage) {
				var canvas = document.createElement("canvas");
				canvas.width = canvas.height = 64;
				var ctx = canvas.getContext("2d");


				ctx.beginPath();
				ctx.moveTo(24,28);
				ctx.lineTo(48,32);
				ctx.lineTo(24,36);
				ctx.lineTo(24,28);

				ctx.moveTo(14,28);
				ctx.arc(16, 28, 2, 0, Math.PI*2, true);

				ctx.moveTo(14,36);
				ctx.arc(16, 36, 2, 0, Math.PI*2, true);

				ctx.closePath();

				ctx.lineWidth = 16;
				ctx.strokeStyle = "rgba(255,0,0, 0.15)";
				ctx.stroke();

				ctx.lineWidth = 12;
				ctx.stroke();

				ctx.lineWidth = 8;
				ctx.stroke();

				ctx.lineWidth = 4;
				ctx.stroke();

				ctx.fillStyle = "rgba(255,0,0,0.8)";
				ctx.fill();

				ctx.strokeStyle = "rgba(255,200,200,1)";
				ctx.lineWidth = 2;
				ctx.stroke();

				Game.SwarmTypes.Predator.protoImage = canvas;
			}

			var img = Game.SwarmTypes.Predator.protoImage;

			oCtx.save();
			oCtx.translate(this.x, this.y);
			oCtx.rotate(-this.angle+Math.PI);
			oCtx.drawImage(img, -32, -32);
			oCtx.restore();
	
		},
		drawTrail : function(oCtx) {
			oCtx.fillRect(this.x-1,this.y-1,2,2);
		}
	}
}

})();


var Digg = (function() {

	var iCallCount = 0;

	var strAPIKey = "";

	var bProxy = true;

	function sendRequest(strEndpoint, oContent, fncCallback) {
		iCallCount++;

		var strURL = "http://" + 
			(bProxy ? ("digg.com/tools/services?endPoint=" + encodeURIComponent(strEndpoint) + "&") : ("services.digg.com" + strEndpoint + "/?"))
			+ "type=javascript"
			+ "&appkey=" + encodeURIComponent(strAPIKey)
			+ "&callback=" + encodeURIComponent("__Digg_Callback_" + iCallCount);
			+ "&time=" + new Date().getTime();

		if (oContent) {
			for (var a in oContent) {
				if (oContent.hasOwnProperty(a)) {
					strURL += "&" + a + "=" + encodeURIComponent(oContent[a]);
				}
			}
		}

		var oScript = document.createElement("script");
		var iThisCall = iCallCount;

		var fncDigg = function(oResponse) {
			document.body.removeChild(oScript);
			if (fncCallback)
				fncCallback(oResponse);

			window["__Digg_Callback_" + iThisCall] = null;
		}

		window["__Digg_Callback_" + iThisCall] = fncDigg;

		oScript.setAttribute("type", "text/javascript");
		document.body.appendChild(oScript);
		oScript.src = strURL;
	}

	return {

		setAPIKey : function(strKey) {
			strAPIKey = strKey;
		},

		callMethod : function(strMethod, oContent, fncCallback) {
			sendRequest(strMethod, oContent, fncCallback);
		}

	}

})();(function() {

Game.Weapons = [

	// rapid fire gun
	{
		lineWidth : 2,
		shotSize : 3,
		shotSpread : 32,
		color : "rgb(220,235,220)",
		damage : 5,
		sound : "sfx_machinegun",
		draw : function(ctx, x,y,sizeX,sizeY) {
			ctx.moveTo(
				x,
				y
			);

			ctx.lineTo(
				x + sizeX,
				y + sizeY
			);
		}
	},

	// big green bullets
	{
		lineWidth : 2,
		shotSize : 10,
		shotSpread : 128,
		color : "rgba(200,255,220,0.5)",
		damage : 10,
		sound : "sfx_machinegun",
		draw : function(ctx, x,y,sizeX,sizeY) {

			ctx.moveTo(
				x,
				y
			);

			ctx.lineTo(
				x + sizeX,
				y + sizeY
			);

			ctx.fillStyle = "rgb(160,200,200)";
			ctx.fillRect(
				x-2.5 + sizeX,
				y-2.5 + sizeY,
				5,5
			);

			ctx.fillStyle = "rgba(255,255,255,0.2)";
			ctx.fillRect(
				x-5 + sizeX,
				y-5 + sizeY,
				10,10
			);
		}
	},

	// blue blobs
	{
		lineWidth : 1,
		shotSize : 10,
		shotSpread : 256,
		color : "rgba(200,255,255,0.5)",
		damage : 20,
		sound : "sfx_machinegun",
		draw : function(ctx, x,y,sizeX,sizeY) {

			ctx.fillStyle = "rgba(155,255,255,0.1)";
			ctx.beginPath()
			ctx.arc(
				x + sizeX*0.9,
				y + sizeY*0.9,
				12,0,Math.PI*2,true
			);
			ctx.closePath()
			ctx.fill();

			ctx.fillStyle = "rgba(155,255,255,0.2)";
			ctx.beginPath()
			ctx.arc(
				x + sizeX*1.1,
				y + sizeY*1.1,
				10,0,Math.PI*2,true
			);
			ctx.closePath()
			ctx.fill();

			ctx.fillStyle = "rgba(155,155,255,0.3)";
			ctx.beginPath()
			ctx.arc(
				x + sizeX*1.2,
				y + sizeY*1.2,
				8,0,Math.PI*2,true
			);
			ctx.closePath()
			ctx.fill();


			ctx.fillStyle = "rgba(255,255,255,0.4)";
			ctx.beginPath()
			ctx.arc(
				x + sizeX*1.3,
				y + sizeY*1.3,
				6,0,Math.PI*2,true
			);
			ctx.closePath()
			ctx.fill();

			ctx.fillStyle = "rgba(255,255,255,0.8)";
			ctx.beginPath()
			ctx.arc(
				x + sizeX*1.4,
				y + sizeY*1.4,
				4,0,Math.PI*2,true
			);
			ctx.closePath()
			ctx.fill();

		}
	}

]


})();