window.onload = function(){
	canvas = document.getElementById('game');
	context = canvas.getContext('2d');
	canvas.style.display = 'block'; // Set up canvas
	canvas.style.border = '1px solid #ddd';
	canvas.style.background = '#fff'; // Set canvas style
	canvas.style.margin = (window.innerHeight > 360 ? window.innerHeight / 2 - 180 + 'px auto' : '10px auto');
	start();
};

window.requestAnimFrame = (function(){
	return  window.requestAnimationFrame	|| 
		window.webkitRequestAnimationFrame	|| 
		window.mozRequestAnimationFrame		|| 
		window.oRequestAnimationFrame		|| 
		window.msRequestAnimationFrame		|| 
		function(callback, element){
			window.setTimeout(callback, 1000 / 60);
		};
})();

function start(){
	reset();
}

function setCookie(name, value, days){
	var date = new Date();
	date.setDate(date.getDate() + days);
	var value = escape(value) + ((days == null) ? "" : "; expires=" + date.toUTCString());
	document.cookie = name + "=" + value;
}

function getCookie(name){
	try{
		var value = document.cookie;
		var start = value.indexOf(" " + name + "=");
		if (start == -1){
			start = value.indexOf(name + "=");
		}
		if (start == -1){
			value = null;
		}else{
			start = value.indexOf("=", start) + 1;
			var end = value.indexOf(";", start);
			if (end == -1){
				end = value.length;
			}
			value = unescape(value.substring(start, end));
		}
		return value;
	}catch(err){
		return null;
	}
}

// Get mouse position
function getMouse(evt){
	var rect = canvas.getBoundingClientRect();
	return {
		x: evt.clientX - rect.left,
		y: evt.clientY - rect.top
	};
}

// Get touch position on mobiles
function getTouch(evt){
	var rect = canvas.getBoundingClientRect();
	var touch = evt.touches[0];
	evt.preventDefault();
	return {
		x: touch.clientX - rect.left,
		y: touch.clientY - rect.top
	};
}

// Set up variables
function reset(){
	// Create arrays
	actors = [];
	controllers = [];
	particles =  [];
	items = [];
	camera = [];
	ais = [];
	keys = [];
	keysDown = [];
	test = [];
	level = ['','','','','','','','','','','','','','','','','','','',''];
	lvDis = [0, 0, 1, 1, 1, 2, 2];
	upgrades = {
		health: [300, 500, 800, 1200, 1700, 2300, 3000],
		energy: [200, 350, 600, 1000, 1500, 2000, 2600],
		powers: [400, 800, 1500, 2500, 4000/*, 6500, 10000*/],
		healthOn: 0,
		energyOn: 0
	}
	unlockKey = [0, 3, 5, 8, 11, 13, 15, 17, 20];
	score = 0;
	lives = 3;
	record = [false, false, false];
	partsInserted = [];
	cookies = false;
	setCookie('test', 'apple', 1);
	if(getCookie('test') == 'apple'){
		cookies = true;
	}
	if(cookies && getCookie('options')){
		try{
			optionvars = JSON.parse(getCookie('options'));
		}catch(err){
			optionvars = [0, 50, 87, 65, 68, 69, 81];
		}
	}else{
		optionvars = [0, 50, 87, 65, 68, 69, 81];
	}
	game = 'menu';
	moveLocked = false;
	lookx = 0;
	message = false;
	endAdded = false;
	ui = {
		select: 0,
		area: 0
	}
	mouse = {
		x: 0,
		y: 0,
		down: false,
		click: false
	};
	tomenu = false;
	slow = false;
	trialComplete = false;
	finTime = false;
	sound = {
		shoot1: new Audio('sfx2.wav'),
		shoot2: new Audio('shoot.wav'),
		shoot3: new Audio('shoot_2.wav'),
		shoot4: new Audio('shoot_3.wav'),
		
		point: new Audio('point.wav'),
		point2: new Audio('point_score_2.wav'),
		point3: new Audio('point_score.wav'),
		
		explode: new Audio('explode.wav'),
		jump: new Audio('jump_up2.wav')
	}
	music = {
		dash: {
			sound: new Audio('Dash.wav'),
			len: 221
		}
	}
	musicPlaying = false;
	musicPlayingID = false;
	musicStartTime = Math.floor(new Date().getTime() / 1000);
	menu = [
		[
			['Play', 4, true],
			['Options', 1, true],
			['Credits', 6, true]
		],
		[
			['Sound levels', 2, true],
			['Controls', 3, true],
			['Back', 0, true]
		],
		[
			['Music', 's', 0, 0, 100, false],
			['Sound', 's', 1, 0, 100, false],
			['Back', 1, true]
		],
		[
			['Jump', 'c', 2], ['Left', 'c', 3],
			['Right', 'c', 4],['Next', 'c', 5],
			['Prev', 'c', 6],['Back', 1, true, 120]
		],
		[	
			['Adventure', 9, true],
			['Time trial', 7, true],
			['Free play', 5, true],
			['Back', 0, true]
		],
		['r', 'play'],
		['t', 'Sint', '', 'Programming and graphics by Markus Scully (Asraelite)', 'Sound by Christian Stryczynski and Asraelite'],
		[
			['"Super Awesome Carrot" by Josh', 8, 0, 'tl'],
			['"Duck" by Asraelite', 8, 1, 'tl'],
			['"Pipes by Asraelite', 8, 2, 'tl'],
			['Back', 4, true]
		],
		['r', 'time'],
		['r', 'adventure']
	];
	lastspeed = 0;
	limitLeft = 16;
	limitRight = 1000000;
	
	if(/Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent)){ // Check if mobile
		mobile = true;
	}else{
		mobile = false;
	}
	
	// type, affiliation, lifespan, xpos, ypos, xvel, yvel
	defineLevels(); // Call function to create level variables
	//level = 2 // Set level
	spritesheet = new Image(); // Define spritesheet
	spritesheet.src = 'actors.png';
	itemsheet = new Image(); // Define spritesheet
	itemsheet.src = 'items.png';
	document.addEventListener('keydown', keyDown, true); // Add key events
	document.addEventListener('keyup', keyUp, true);
	if(mobile){
		document.body.addEventListener('touchstart', function(evt){mouse.down = true}, false);
		document.body.addEventListener('touchend', function(evt){mouse.down = false}, false);
		document.body.addEventListener('touchmove', function(evt){mouse.x = getTouch(evt).x; mouse.y = getTouch(evt).y}, false);
	}else{
		document.addEventListener('mousedown', function(evt){mouse.down = true}, false);
		document.addEventListener('mouseup', function(evt){mouse.down = false}, false);
		document.addEventListener('mousemove', function(evt){mouse.x = getMouse(evt).x; mouse.y = getMouse(evt).y}, false);
	}
	
	animate();
}

function toMenu(){
	actors = [];
	controllers = [];
	particles =  [];
	camera = [];
	ais = [];
	items = [];
	keys = [];
	level = ['','','','','','','','','','','','','','','','','','','',''];
	partsInserted = [];
	game = 'menu';
	ui.area = 0;
	ui.select = 0;
	score = 0;
	lives = 3;
	tomenu = false;
}

function play(){
	// Create player and its key controller
	limitLeft = 16;
	limitRight = 1000000000;
	actors[0] = new Actor(0, 0, 200, 8, 200, 3, limitLeft + 112, 64, 16, 16);
	controllers[0] = new Controller(actors[0], [[optionvars[4], 'moveRight'], [optionvars[3], 'moveLeft'], [optionvars[2], 'jump'], [27, 'quit'], [90, 'suicide', 0], ['c', 'current'], [optionvars[5], 'next', 0], [optionvars[6], 'prev', 0]]);
	
	particles[0] = new Particle('mouse', 0, 'mouse', 10000000000, 0, 0, 0, 0, 0, [0, 0]); // Create reticule]
	
	if(cookies){
		setCookie('options', JSON.stringify(optionvars), 30);
	}
	totalLevelScore = 0;
	totalEnemies = 0;
	finTime = false;
	
	endAdded = false;
	
	camera = [actors[0]]; // Set camera.
}

function newLevel(){
	levelNo += 1;
	endAdded = false;
	level = ['','','','','','','','','','','','','','','','','','','',''];
	partsInserted = [];
	ais = [];
	items = [];
	totalLevelScore = 0;
	totalEnemies = 0;
	actors.splice(1, actors.length - 1);
	particles.splice(1, particles.length - 1);
	limitLeft = 16;
	limitRight = 1000000000;
	actors[0].x = limitLeft + 112;
	actors[0].y = 64;
	actors[0].health = actors[0].maxhealth;
	actors[0].energy = actors[0].maxenergy;
}

function animate() {
	requestAnimFrame(animate);
	loopGame();
}

function control(n){
	if(actors[n]){
		camera = [actors[n]];
		for(var i in ais){
			if(ais[i].actor == actors[n]){
				ais.splice(i, 1);
			}
			controllers[0] = new Controller(actors[n], [[optionvars[4], 'moveRight'], [optionvars[3], 'moveLeft'], [optionvars[2], 'jump'], [27, 'quit'], [90, 'suicide', 0], ['c', 'current'], [optionvars[5], 'next', 0], [optionvars[6], 'prev', 0]]);
		}
		
	}else{
		return false;
	}
}

// Rounds a number.
function r(num){
	return Math.round(num);
}

function spawn(no, enemy){
	enemy = typeof enemy === 'undefined' ? 1 : enemy;
	no = typeof no === 'undefined' ? 1 : no;
	for(i = 0 ; i < no; i++){
		var x = lookx + 250 + ((Math.random() - 0.5) * 200);
		switch(enemy){
			case 0:
				actors[actors.length] = new Actor(8, 1, 80, 5, 80, 3, x, 0, 16, 16);
				ais[ais.length] = new Ai(actors.length - 1, 'pace');
				break;
			case 1:
				actors[actors.length] = new Actor(9, 1, 100, 6, 50, 3, x, 0, 16, 16);
				ais[ais.length] = new Ai(actors.length - 1, 'alphaBot');
				break;
		}
	}
}

function butcher(){
	actors = [actors[0]];
	ais = [];
}

function controlChar(key){
	var legend = {
		16 : 'Shift',
		17 : 'Control',
		18 : 'Alt',
		32 : 'Space',
		37 : 'L-Arrow',
		38 : 'U-Arrow',
		39 : 'R-Arrow',
		40 : 'D-Arrow'
	}
	
	if(key >= 48 && key <= 90){
		return String.fromCharCode(key);
	}else{
		var keyChar = legend[key];
		return (typeof keyChar === 'undefined' ? key : keyChar);
	}
}

// Converts "65324" into "06:53.240" etc.
function toClock(num, precision){
	var min = Math.floor(num / 60000);
	if(min < 10){
		min = "0" + min;
	}
	var sec = num % 60000; // 6163
	var sec = r(sec / Math.pow(10, 3 - precision));
	sec = (sec * Math.pow(10, 3 - precision)) / 1000;
	
	var disSec = sec;
	for(i = 0; i < precision; i++){
		if(sec % 1 / Math.pow(10, i) == 0){
			disSec += (i == 0 ? ".0" : "0");
		}
	}
	
	if(sec < 10){
		disSec = "0" + disSec;
	}
	return min + ":" + disSec;
}

function setStrChar(string , index, letter) {
    if(index > string.length-1){
		return string;
	}
    return string.substr(0, index) + letter + string.substr(index + 1);
}

// Generate random number from seed (not used)
function seed(num){
	return ((num * 467 + ((num * 6) % 9)) % 1000) / 1000;
}

// Add key pressed to key list.
function keyDown(e){
	var keyPress;
	if (typeof event !== 'undefined') {
		keyPress = event.keyCode;
	}else if(e){
		keyPress = e.which;
	}
	if(keys.indexOf(keyPress) == -1){
		keys.push(keyPress);
		keysDown.push(keyPress);
	}
}

// Remove key from key list.
function keyUp(e){
	var keyPress;
	if (typeof event !== 'undefined') {
		keyPress = event.keyCode;
	}else if(e){
		keyPress = e.which;
	}
	keys.splice(keys.indexOf(keyPress), 1);
}

// Define objects.

function Controller(object, actions){
	this.actor = object;
	this.checkKeys = function(){
		for(i in actions){
			if(actions[i].length > 2 && actions[i][2] == 0){
				if(keysDown.indexOf(actions[i][0]) > -1){
					this.actor.action(actions[i][1]);
				}
			}else{
				if(keys.indexOf(actions[i][0]) > -1){
					this.actor.action(actions[i][1]);
				}
			}
			if(mouse.down && actions[i][0] == 'c' && singleActionUsed == false){
				if(this.actor.action(actions[i][1])){
					singleActionUsed = true;
				}
			}
		}
		if(!mouse.down){
			singleActionUsed = false;
		}
		this.actor.refreshActions();
	}
}

function Ai(index, ai){
	this.index = index;
	this.actor = actors[index];
	this.aivars = [0, 0, 0];
	this.action = function(act){
		this.actor.action(act);
	}
	this.run = function(){
		switch(ai){
			case 'alphaBot': // Basic following and jumping
				var topDistance = 400;
				var distanceAway = Math.abs(actors[0].x - this.actor.x);
				if(distanceAway < topDistance){
					if(this.aivars[0] == 1){
						this.aivars[1] = 120;
					}else{
						this.aivars[1] = 300;
					}
					if(this.aivars[0] == 1 && distanceAway < 150){
						var angle = Math.atan2(actors[0].y - ((this.actor.y) + (distanceAway / 10)), actors[0].x - this.actor.x);
						this.actor.vars[0] = angle;
						this.action('en1');
					}
					if(this.aivars[0] == 1 && this.actor.energy <= 5){
						this.aivars[0] = 0;
					}
					if(this.aivars[0] == 0 && this.actor.energy >= this.actor.maxenergy){
						this.aivars[0] = 1;
					}
					try{
					if((level[(this.actor.y) >> 4][((this.actor.x + 8) >> 4) + (this.aivars[0] == 0 ? 1 : -1)] != '#' && actors[0].y <= this.actor.y) || this.actor.box.inlava){
						this.action('jump');
					}
					}catch(err){
					}
					var inverter = (distanceAway > this.aivars[1] ? 0 : 1);
					if(Math.abs(distanceAway - this.aivars[1]) > 20 && Math.random() < 0.9){
						if(this.actor.x > actors[0].x){
							this.action(['moveLeft', 'moveRight'][inverter]);
						}else{
							this.action(['moveRight', 'moveLeft'][inverter]);
						}
						if(this.actor.x == this.aivars[2]){
							this.action('jump');
						}
						this.aivars[2] = this.actor.x;
					}
				}
				break;
			case 'pace': // Walking back and forth
				if(this.actor.xvel == 0){
					this.aivars[0] = (1 - this.aivars[0]);
				}
				try{
					if(level[(this.actor.y) >> 4][((this.actor.x + 8) >> 4) + (this.aivars[0] == 0 ? 1 : -1)] != '#'){
						this.aivars[0] = (1 - this.aivars[0]);
					}
				}catch(err){
				}
				if(this.actor.box.inlava){
					this.action('jump');
				}
				var distanceAway = Math.abs(actors[0].x - this.actor.x);
				if(distanceAway < 100){
					var angle = Math.atan2(actors[0].y - ((this.actor.y) + (distanceAway / 10)), actors[0].x - this.actor.x);
					this.actor.vars[0] = angle;
					this.action('en2');
				}
				this.action(this.aivars[0] == 0 ? 'moveRight' : 'moveLeft');
				break;
			case 'fly': // Flying AI
				if(this.aivars[1]){
					this.aivars[0] = 6;
				}else{
					this.aivars[0] = 3;
				}
				var bottom = false;
				var aboveBottom = false;
				try{
				for(var i = -3; i <= 3; i++){
					for(var j = 1; j < this.aivars[0] + 1; j++){
						if(level[((this.actor.y) >> 4) + j][((this.actor.x + 8) >> 4) + i] == '#'){
							if(j <= this.aivars[0]){
								bottom = true;
							}else{
								aboveBottom = true;
							}
						}
					}
				}
				}catch(err){
					bottom = false;
				}
				if(this.actor.y <= 30){
					bottom = false;
				}
				if(!this.aivars[2]){
					this.aivars[2] = 70 + (Math.random() * 100);
				}
				var distanceAway = Math.abs(actors[0].x - this.actor.x);
				if(distanceAway < 500 && distanceAway > this.aivars[2]){
					this.action(actors[0].x > this.actor.x ? 'moveRight' : 'moveLeft');
				}
				if(distanceAway < 50 + this.aivars[2] && !this.aivars[1] && Math.random() < 0.5){
					var angle = Math.atan2(actors[0].y - this.actor.y, actors[0].x - this.actor.x);
					this.actor.vars[0] = angle;
					this.action('en3');
				}
				if(this.actor.energy <= 5){
					this.aivars[1] = true;
				}
				if(this.actor.energy == this.actor.maxenergy){
					this.aivars[1] = false;
				}
				if(bottom){
					this.actor.yvel -= 0.2;
				}else{
					if(aboveBottom){
						this.actor.yvel *= 0.8;
					}else{
						this.actor.yvel += 0.2;
					}
				}
				this.actor.yvel *= 0.98;
				if(Math.abs(this.actor.yvel) >= 2){
					this.actor.yvel *= 0.7;
				}
				break;
			case 'boss':
				
				break;
			case 'still':
				break;
			case 'test': // Jumping AI
				this.action('jump');
				break;
		}
		
		if(this.actor.health < 0){
			this.deleteme = true;
			this.actor.deleteme = true;
			var w = this.actor.w / 2;
			var h = this.actor.h / 2;
			for(i = 0; i < w * h; i++){
				particles.push(new Particle(0, 2, 2, Math.random() * 500 + 2500, this.actor.x + ((i % w) * 2), this.actor.y - ((i % h) * 2), (Math.random() - 0.5) * 10, (Math.random() - 0.8) * 10, 0.4, [0.99, 0.99]));
			}
			score += [20, 50, 100, 1000, 0, 0, 0, 0][this.actor.image - 8];
			this.actor.x = 0;
			this.actor.y = 0;
		}
	}
}

// Actor class for all solid cubes
function Actor(image, type, health, moveSpeed, energy, powers, xpos, ypos, width, height, ai, gravity){
	this.image = image;
	this.ai = typeof ai === 'undefined' ? false : ai;
	this.group = type;
	this.gravity = typeof gravity === 'undefined' ? true : gravity;
	this.health = health;
	this.maxhealth = health;
	this.energy = energy;
	this.maxenergy = energy;
	this.speed = moveSpeed;
	this.tookDamage = 0;
	this.select = 0;
	this.powers = 6;
	this.yvel = 0;
	this.xvel = 0;
	this.imageLoad = 2;
	this.deleteme = false;
	this.right = this.up = this.down = false;
	this.left = false;
	this.x = xpos;
	this.y = ypos;
	this.w = width;
	this.h = height;
	this.box = new Box(this.x, this.y, this.w, this.h, this.xvel, this.yvel, 0, this.gravity);
	this.oneactions = [];
	this.actionsturn = [];
	this.index = actors.length;
	this.vars = [false, false, false]; // for use by AIs to control particles
	this.test = '';
	
	this.refreshActions = function(){
		this.oneactions = [];
		for(i in this.actionsturn){
			this.oneactions.push(this.actionsturn[i]);
		}
	}
	
	// Actions to call from controllers
	this.action = function(type){
		if(!message){
			var angle = Math.atan2(-(this.y - mouse.y), (mouse.x - ((this.x - lookx))));
			var distanceToSound = Math.abs(this.x - lookx - 250);
			switch(type){
				case 0:
					if(this.energy >= 2){
						particles.push(new Particle(0, this.type, 0, Math.random() * 500 + 5000, this.x + 8, this.y - 8, Math.cos(angle) * 15 + this.xvel, Math.sin(angle) * 15 + this.yvel, 0.4, [0.997, 0.997]));
						if(distanceToSound < 500){
							sound.shoot1.volume = (r(distanceToSound < 100 ? 1 : (500 - distanceToSound) / 400) * optionvars[1]) / 100;
							sound.shoot1.play();
						}
						this.energy -= 2;
					}
					break;
				case 1:
					if(this.energy >= 3){
						particles.push(new Particle(1, this.type, 4, Math.random() * 500 + 4500, this.x + 8, this.y - 8, Math.cos(angle + ((Math.random() - 0.5) * 0.03)) * 8, Math.sin(angle + ((Math.random() - 0.5) * 0.03)) * 8, 0, [0.999, 0.999], true, -0.3));
						if(distanceToSound < 500){
							sound.shoot1.volume = (r(distanceToSound < 100 ? 1 : (500 - distanceToSound) / 400) * optionvars[1]) / 100;
							sound.shoot1.play();
						}
						this.energy -= 3;
					}
					break;
				case 2:
					if(this.energy >= 100){
						particles.push(new Particle(2, 2, 5, 200000, this.x + 8, this.y - 8, Math.cos(angle + ((Math.random() - 0.5) * 0.03)) * 12, Math.sin(angle + ((Math.random() - 0.5) * 0.03)) * 12, 0.5, [0.9975, 0.9975], false));
						if(distanceToSound < 500){
							sound.shoot1.volume = (r(distanceToSound < 100 ? 1 : (500 - distanceToSound) / 400) * optionvars[1]) / 100;
							sound.shoot1.play();
						}
						this.energy -= 100;
					}
					break;
				case 3:
					if(this.energy >= 80){
						this.xvel = Math.cos(angle) * 18;
						this.yvel = Math.sin(angle) * 15;
						angle += Math.PI - 0.5;
						for(var i = 0; i < 50; i++){
							particles.push(new Particle(0, this.type, 7, 2500 + Math.random() * 1000, this.x + 8, this.y - 8, Math.cos(angle) * 3, Math.sin(angle) * 3, 0.5, [0.9985, 0.9985], false, -0.8));
							angle += 0.02;
						}
						this.energy -= 80;
						sound.shoot1.volume = (r(distanceToSound < 100 ? 1 : (500 - distanceToSound) / 400) * optionvars[1]) / 100;
						sound.shoot1.play();
					}
					break;
				case 4:
					if(this.energy >= 5){
						particles.push(new Particle(4, 2, 8, 20000, this.x + 8, this.y - 8, Math.cos(angle + ((Math.random() - 0.5) * 0.03)) * 10, Math.sin(angle + ((Math.random() - 0.5) * 0.03)) * 10, 0, [0.999, 0.999], true, -0.6));
						if(distanceToSound < 500){
							sound.shoot1.volume = (r(distanceToSound < 100 ? 1 : (500 - distanceToSound) / 400) * optionvars[1]) / 100;
							sound.shoot1.play();
						}
						this.energy -= 5;
					}
					break;
				case 5:
					if(this.energy >= 6){
						particles.push(new Particle(5, this.type, 10, Math.random() * 500 + 5000, this.x + 8, this.y - 8, Math.cos(angle) * 18 + this.xvel, Math.sin(angle) * 18 + this.yvel, 0.25, [0.9985, 0.9985]));
						if(distanceToSound < 500){
							sound.shoot1.volume = (r(distanceToSound < 100 ? 1 : (500 - distanceToSound) / 400) * optionvars[1]) / 100;
							sound.shoot1.play();
						}
						this.energy -= 6;
					}
					break;
				case 'en1':
					if(this.energy >= 2){
						particles.push(new Particle(0, 1, 1, Math.random() * 500 + 5000, this.x + 8, this.y - 8, Math.cos(this.vars[0]) * 15 + this.xvel, Math.sin(this.vars[0]) * 15 + this.yvel, 0.4, [0.995, 0.995]));
						if(distanceToSound < 500){
							sound.shoot1.volume = (r(distanceToSound < 100 ? 1 : (500 - distanceToSound) / 400) * optionvars[1]) / 100;
							sound.shoot1.play();
						}
						this.energy -= 2;
					}
					break;
				case 'en2':
					if(this.energy >= 2){
						particles.push(new Particle(0, 1, 1, Math.random() * 500 + 3000, this.x + 8, this.y - 8, Math.cos(this.vars[0]) * 12 + this.xvel, Math.sin(this.vars[0]) * 12 + this.yvel, 0.4, [0.994, 0.994]));
						if(distanceToSound < 500){
							sound.shoot1.volume = (r(distanceToSound < 100 ? 1 : (500 - distanceToSound) / 400) * optionvars[1]) / 100;
							sound.shoot1.play();
						}
						this.energy -= 2;
					}
					break;
				case 'en3':
					if(this.energy >= 3){
						particles.push(new Particle(1, 1, 9, Math.random() * 500 + 3000, this.x + 8, this.y - 8, Math.cos(this.vars[0]) * 10 + this.xvel, Math.sin(this.vars[0]) * 10 + this.yvel, 0, [0.9987, 0.9987], true, -0.6));
						if(distanceToSound < 500){
							sound.shoot1.volume = (r(distanceToSound < 100 ? 1 : (500 - distanceToSound) / 400) * optionvars[1]) / 100;
							sound.shoot1.play();
						}
						this.energy -= 3;
					}
					break;
				case 'moveLeft':
					this.xvel -= ((this.speed / 100) * speed);
					break;
				case 'moveRight':
					this.xvel += ((this.speed / 100) * speed);
					break;
				case 'jump':
					this.box.y += 1;
					if(this.box.collide() || this.box.inlava){
						this.yvel = -6.8 * (this.box.inlava ? 0.2 : 1);
						if(distanceToSound < 300){
							sound.jump.volume = (r(distanceToSound < 100 ? 1 : (300 - distanceToSound) / 200) * optionvars[1]) / 100;
							sound.jump.play();
						}
					}
					this.box.y -= 1;
					break;
				case 'current':
					this.action(this.select);
					if(this.select == 2 || this.select == 3){
						return true;
					}
					break;
				case 'prev':
					if(this.powers > 1){
						this.select = (this.select + 1) % (this.powers);
					}else{
						this.select = 0;
					}
					break;
				case 'next':
					this.select = (this.select <= 0 ? (this.powers - 1) : this.select - 1);
					break;
				case 'suicide':
					this.health = (this.y > 50 ? 0 : this.health);
					break;
				case 'quit':
					tomenu = true;
					break;
			}
			return false;
		}
		this.actionsturn.push(type);
	}
	
	this.simulate = function(){
		this.box.xvel = this.xvel;
		this.box.yvel = this.yvel;
		this.box.x = this.x;
		this.box.y = this.y;
		this.box.health = this.health;
		this.box.run();
		this.x = this.box.x;
		this.y = this.box.y;
		this.xvel = this.box.xvel;
		this.yvel = this.box.yvel;
		this.health = this.box.health;
		if(this.health <= 0){
			if(this.image == 0){
				for(i = 0; i < 64; i++){
					particles.push(new Particle(0, 0, 0, Math.random() * 500 + 2500, this.x + ((i % 8) * 2), this.y - ((i % 8) * 2), (Math.random() - 0.5) * 10, (Math.random() - 0.8) * 10, 0.4, [0.99, 0.99]));
				}
				if(gameMode == 'free'){
					this.y = -500;
				}else{
					if(--lives >= 0){
						this.x = limitLeft + 112;
						this.y = 64;
					}else{
						message = ['Game Over', 'You have 0 lives left :(', 'Score: ' + score];
						this.y = -10;
						lives++;
					}
				}
				this.health = this.maxhealth;
				score -= 50 * levelNo;
				this.xvel = 0;
				this.yvel = 0;
				clockStart = new Date().getTime();
			}
		}
		if(this.energy >= this.maxenergy){
			this.energy = this.maxenergy;
		}else{
			this.energy += 0.04 * speed;
		}
		//this.xvel *= Math.pow(0.992, speed);
	}
	
	this.draw = function(){
		var drawx = r(this.x - lookx + this.xvel);
		var drawy = 200;
		context.drawImage(spritesheet, (this.image % 8) * 16, Math.floor(this.image / 8) * 16, this.w, this.h, drawx, r(this.y - 16 - looky), this.w, this.h);
		context.globalAlpha = 1;
		if(this.tookDamage > 0 && this.image != 0){
			context.strokeStyle = '#555';
			context.fillStyle = '#c54';
			context.fillRect(r((this.x - lookx) + (this.w / 2) + camera[0].xvel) - 10 - 0.5, r(this.y) - (15.5 + (this.h / 2)), (this.health / this.maxhealth) * (this.w * 1.2), 5);
			context.strokeRect(r((this.x - lookx) + (this.w / 2) + camera[0].xvel) - 10 - 0.5, r(this.y) - (15.5 + (this.h / 2)), this.w * 1.2, 5);
			this.tookDamage -= (1 / this.w) * speed;
		}
	}
}

function Item(type, xpos, ypos){
	this.x = xpos;
	this.y = ypos;
	this.type = type;
	this.hover = 0;
	this.deleteme = false;
	
	this.draw = function(){
		var drawx = r(this.x - lookx);
		context.drawImage(itemsheet, this.type * 16, 0, 16, 16, drawx, r(this.y + r(Math.sin(this.hover) * 2)), 16, 16);
		context.globalAlpha = 1;
	}
	
	this.run = function(){
		this.hover += 0.005 * speed;
		act = actors[0];
		if(this.y + 16 < act.y + act.h && this.y + 32 > act.y){
			if(this.x + 16 > act.x && this.x < act.x + act.w){
				switch(this.type){
					case 0:
						if(actors[0].health < actors[0].maxhealth){
							actors[0].health += 100;
							if(actors[0].health > actors[0].maxhealth){
								actors[0].health = actors[0].maxhealth;
							}
							sound.point3.volume = optionvars[1] / 150;
							sound.point3.play();
						}
						break;
					case 1:
						lives += 1;
						sound.point3.volume = optionvars[1] / 150;
						sound.point3.play();
						break;
					case 2:
						score += 5;
						sound.point.volume = optionvars[1] / 150;
						sound.point.play();
						break;
					case 3:
						score += 10;
						sound.point.volume = optionvars[1] / 150;
						sound.point.play();
						break;
					case 4:
						score += 20;
						sound.point.volume = optionvars[1] / 150;
						sound.point.play();
						break;
					case 5:
						score += 50;
						sound.point.volume = optionvars[1] / 150;
						sound.point.play();
						break;
					case 6:
						score += 100;
						sound.point.volume = optionvars[1] / 150;
						sound.point.play();
						break;
					case 7:
						score += 300;
						sound.point.volume = optionvars[1] / 150;
						sound.point.play();
						break;
				}
				this.deleteme = true;
			}
		}
	}
}

function Particle(type, affiliation, drawType, lifespan, xpos, ypos, xvel, yvel, gravity, airRes, actDeath, bounce){
	this.gravity = typeof gravty !== 'undefined' ? gravity : 1;
	this.x = xpos;
	this.y = ypos;
	this.drawType = drawType;
	this.actDeath = typeof actDeath === 'undefined' ? true : actDeath;
	this.bounce = typeof bounce === 'undefined' ? 0 : bounce;
	this.xvel = xvel;
	this.yvel = yvel;
	this.type = type;
	this.air = airRes;
	this.life = lifespan;
	this.sizes = [3, 5, 5, 3, 5, 3];
	this.size = this.sizes[type];
	this.created = this.timeup = new Date();
	this.timeup = new Date(this.timeup.getTime() + lifespan);
	this.deleteme = false;
	this.aff = affiliation;
	this.vars = [false, false];
	var angle = Math.random() * 360;
	this.addx = Math.sin(angle) * ((particles.length + 200) / 5);
	this.addy = Math.cos(angle) * ((particles.length + 200) / 10);
	this.box = new Box(this.x, this.y, this.size, this.size, this.xvel, this.yvel, 1, gravity, this.air, this.bounce);
	this.box.unstuck();
	
	this.drawBox = function(alpha, width, color, size, fill, rel){
		context.globalAlpha = alpha;
		context.lineWidth = width;
		context.beginPath();
		context.strokeStyle = color;
		var add = (width % 2 == 0 ? 0 : 0.5);
		var rel = (typeof rel === 'undefined' ? 1 : rel);
		context.rect(r(this.x - (lookx * rel)) + add, r(this.y) + add, size, size);
		if(typeof fill !== 'undefined'){
			if(fill != false){
				context.fillStyle = fill;
				context.fill();
			}
		}
		context.stroke();
		context.globalAlpha = 1;
	};
	
	this.draw = function(extendSize){
		extendSize = (typeof extendSize === 'undefined' ? 1 : extendSize);
		if(this.x > lookx - 50 && this.x < lookx + 550 && this.y < 300 && this.y > -50){
			switch(this.drawType){
				case 'mouse':
					context.globalAlpha = 0.7;
					context.lineWidth = 2;
					context.strokeStyle = '#33d';
					context.strokeRect(mouse.x - this.vars[0] / 2, mouse.y - this.vars[0] / 2, this.vars[0], this.vars[0]);
					break;
				case 0:
					this.drawBox(1, 1 * extendSize, '#66b', 2 * extendSize);
					break;
				case 1:
					this.drawBox(1, 1 * extendSize, '#f87', 2 * extendSize);
					break;
				case 2:
					this.drawBox(1, 1 * extendSize, '#655', 2 * extendSize);
					break;
				case 3:
					this.drawBox(0.5, 1 * extendSize, '#000', 4 * extendSize);
					break;
				case 4:
					this.drawBox(0.8, 1 * extendSize, '#229', 3 * extendSize);
					break;
				case 5:
					this.drawBox(1, 2 * extendSize, '#93b', 5 * extendSize, '#b7f');
					break;
				case 6:
					this.drawBox(1, 1 * extendSize, '#93b', 2 * extendSize);
					break;
				case 7:
					this.drawBox(1, 1 * extendSize, '#f95', 2 * extendSize);
					break;
				case 8:
					this.drawBox(1, 2 * extendSize, '#7bb', 3 * extendSize);
					break;
				case 9:
					this.drawBox(1, 1 * extendSize, '#b32', 4 * extendSize);
					break;
				case 10:
					this.drawBox(1, 1 * extendSize, '#363', 2 * extendSize);
					break;
			}
			context.globalAlpha = 1;
		}
	}
	
	this.drawIcons = function(){
		for(var k = 0; k < 6; k++){
			this.x = 480.5 - (35 * k);
			this.y = 293.5;
			var unlocked = ((actors[0].powers - 1) >= k ? 1 : 5);
			switch(k){
				case 0:
					this.x -= 2;
					this.y -= 2;
					this.drawBox((1 / unlocked), 1.5, '#66b', 4, false, 0);
					break;
				case 1:
					this.x -= 3;
					this.y -= 3;
					this.drawBox((0.8 / unlocked), 2, '#229', 6, false, 0);
					break;
				case 2:
					this.x -= 5;
					this.y -= 5;
					this.drawBox((1 / unlocked), 3, '#93b', 10, '#b7f', 0);
					break;
				case 3:
					this.x -= 2;
					this.y -= 6;
					this.drawBox((1 / unlocked), 1.5, '#f95', 4, false, 0);
					this.x += 6;
					this.y += 8;
					this.drawBox((1 / unlocked), 1.5, '#f95', 4, false, 0);
					this.x -= 12;
					this.drawBox((1 / unlocked), 1.5, '#f95', 4, false, 0);
					break;
				case 4:
					this.x -= 5;
					this.y -= 5;
					this.drawBox((1 / unlocked), 3, '#7bb', 10, false, 0);
					break;
				case 5:
					this.x -= 2;
					this.y -= 2;
					this.drawBox((1 / unlocked), 1.5, '#363', 4, false, 0);
					break;
			}
		}
		this.drawType = 'mouse';
		this.x = lookx;
		this.y = 0;
	}
	
	this.simulate = function(){
		var distanceToSound = Math.abs(this.x - lookx - 250);
		switch(this.type){
			case 'mouse':
				if(this.vars[0] == false){
					this.vars[0] = 8;
				}
				if(this.vars[1] == true){
					this.vars[0] += 0.4;
					if(this.vars[0] >= 10){
						this.vars[1] = false;
					}
				}else{
					this.vars[0] -= 0.4;
					if(this.vars[0] <= 7){
						this.vars[1] = true;
					}
				}
				this.x = lookx;
				if(mouse.down){
					if(mobile){
						if(mouse.y < 100){
							actors[0].action('jump');
						}
						if(mouse.x > 300){
							actors[0].action('moveRight');
						}else if(mouse.x < 200){
							actors[0].action('moveLeft');
						}
					}
					this.vars[0] = 6;
				}
				break;
			case 0:
				break;
			case 1:
				break;
			case 2:
				if(this.yvel == 0){
					this.deleteme = true;
					for(var i = 0; i < 100; i++){
						particles[particles.length] = new Particle(3, 2, 6, Math.random() * 500 + 2000, this.x, this.y, (Math.random() - 0.5) * 18, (Math.random() - 0.9) * 9, 0.4, [0.998, 0.998])
					}
					sound.explode.volume = (r(distanceToSound < 100 ? 1 : (500 - distanceToSound) / 400) * optionvars[1]) / 400;
					sound.explode.play();
				}
				break;
			case 3:
				break;
			case 4:
				for(var i in actors){
					if(i > 0){
						var a = actors[i];
						var dis = Math.sqrt(((a.x - this.x) * (a.x - this.x)) + ((a.y - this.y) * (a.y - this.y))) + 10;
						var pull = 1 / Math.pow(dis, 2);
						var angleTo = Math.atan2(a.y - this.y, a.x - this.x);
						this.xvel += 10000 * Math.cos(angleTo) * pull;
						this.yvel += 10000 * Math.sin(angleTo) * pull;
					}
				}
				break;
			case 5:
				break;
			default:
				break;
		}
		
		for(j in actors){
			var act = actors[j];
			if(this.y + 16 < act.y + act.h && this.y + 16 + this.size > act.y){
				if(this.x + this.size > act.x && this.x < act.x + act.w && (this.aff == 1 ? j == 0 : j > 0)){
					actors[j].health -= (Math.abs(this.xvel) + Math.abs(this.yvel)) / [10, 3, 0.2, 2, 8, 3, 0.5][this.type];
					actors[j].xvel += this.xvel / 8;
					actors[j].yvel += this.yvel / 8;
					actors[j].tookDamage = 40;
					this.deleteme = true;
				}
			}
		}
		// this.y < obj.y + obj.h && this.y + obj.h > obj.y && this.x + obj.w > obj.x && this.x < obj.x + obj.w && obj.box != this)
		
		if(thisLoop > this.timeup){
			this.deleteme = true;
		}
		this.box.xvel = this.xvel;
		this.box.yvel = this.yvel;
		this.box.x = this.x;
		this.box.y = this.y + 16;
		this.box.run();
		this.x = this.box.x;
		this.y = this.box.y - 16;
		this.xvel = this.box.xvel;
		this.yvel = this.box.yvel;
	}
}

// Collision detection class
function Box(x, y, w, h, xvel, yvel, colgroup, gravity, airRes, bounce){
	this.x = x;
	this.y = y;
	this.width = w;
	this.height = h;
	this.col = colgroup;
	this.right = false;
	this.left = false;
	this.up = false;
	this.down = false;
	this.gravity = gravity;
	this.air = (typeof airRes === 'undefined' ? [0.99, 1] : airRes);
	this.bounce = (typeof bounce === 'undefined' ? 0 : bounce);
	this.health = 0;
	this.inlava = false;
	this.setX = false;
	this.setY = false;
	
	this.reset = function(){
		this.right = false;
		this.left = false;
		this.up = false;
		if(!this.inlava){
			this.down = false;
		}
	}
	
	this.collide = function(){
		// Check for collision with level
		var lv = level;
		var colareax = ((this.width - 2) >> 4) + 2;
		var colareay = ((this.height - 2) >> 4) + 2;
		var collision = false;
		var type = 'level';
		this.inlava = false;
		this.setX = false;
		this.setY = false;
		test = [];
		for(var hr = 0; hr < colareax; hr++){
			for(var vr = 0; vr < colareay; vr++){
				var xcol = (((this.x - (hr == colareax - 1 ? 1 + 16 - (((this.width - 1) % 16) + 1): 0)) >> 4) + hr); // This is a bit complicated...
				var ycol = (((this.y - (vr == colareay - 1 ? 1 + 16 - (((this.height - 1) % 16) + 1) : 0)) >> 4) + vr); // It will get the number of 16x16...
				if(ycol - 1 >= 0 && ycol <= lv.length){ // Blocks it takes to cover the entire actor or particle. E.g. something 8x8 needs 2 xcol and 2 ycol,
					try{
						if(xcol >= 0 && xcol < lv[ycol].length){ // This is because it could be on the border between 2 blocks, covering up to 4 of them.
							if(lv[ycol - 1][xcol] == '#'){ // If normal block
								collision = true;
							}else if(lv[ycol - 1][xcol] == 'x'){ // If in lava
								this.health -= 0.01 * speed;
								this.inlava = true;
							
								this.xvel *= Math.pow(0.997, speed); // Slow down velocity
								this.yvel *= Math.pow(0.997, speed);
							}else if(lv[ycol - 1][xcol] == 'w'){ // If in water
								this.inlava = true;
								this.xvel *= Math.pow(0.999, speed);
								this.yvel *= Math.pow(0.999, speed);
							}else if(lv[ycol - 1][xcol] == 'F' && actors[0].box == this){
								if(gameMode == 'time'){
									if(trialComplete == false){
										var time = r((new Date().getTime() - clockStart));
										finTime = toClock(time, 3);
										rawFinTime = time;
										if(cookies){
											record[levelNo] = getCookie('trial record: ' + levelNo);
										}
										if(rawFinTime < record[levelNo] || record[levelNo] == false){
											if(cookies){
												setCookie('trial record: ' + levelNo, rawFinTime, 30);
											}
											record[levelNo] = rawFinTime;
										}
										message = ['Level completed', 'Time : ' + finTime, 'Record : ' + toClock(record[levelNo], 3)]; // When finished time trial
									}
									trialComplete = true;
								}else{
									message = 'win';
								}
							}
						}
					}catch(err){
						console.error('Physics error at ' + xcol + ', ' + ycol + '. ' + particles.length + '\n' + err);
						this.y = 0;
					}
				}
			}
		}
		if(this.col == 0){
			for(j in actors){ // Check if colliding with another actor
				var obj = actors[j];
				if(this.y < obj.y + obj.h && this.y + this.height > obj.y && this.x + this.width > obj.x && this.x < obj.x + obj.w && obj.box != this){
					collision = true;
					var inv = this.xvel > 0;
					if(this.y < obj.y - this.height - 1 || this.y > obj.y + obj.h + 1){
						this.setY = obj.y + (inv ? -this.height : obj.h);
					}else{
						this.setX = (obj.x + (inv ? -this.width : obj.w) + obj.xvel);
					}
				}
			}
		}
		
		if(this.x <= 16 || this.x > level[0].length << 4){ // If at the edge of the level, collide.
			collision = true;
		}
		
		return collision;
	}
	
	this.move = function(){
		if(!this.inlava){
			this.down = false;
		}
		var apparentVel = (this.xvel * speed) / (1000 / 60); // Velocity adapted to frame rate
		var velToKill = Math.abs(apparentVel)
		var maxMove = Math.floor(this.width / 2);
		while(velToKill > 0){ // If velocity is more than half the box size, only move in increments of half box size to prevent clipping
			if(velToKill > maxMove){
				this.x += (this.xvel > 0 ? maxMove : -maxMove);
				velToKill -= maxMove;
			}else{
				this.x += (this.xvel > 0 ? velToKill : -velToKill);
				velToKill = 0;
			}
			if(this.collide() && Math.abs(this.xvel) > 0){
				if(this.setX){
					this.x = this.setX;
				}else{
					this.x = ((this.x >> 4) << 4) + (this.xvel > 0 ? 16 - (((this.width - 1) % 16) + 1) : 16);
				}
				this.xvel = this.xvel * this.bounce;
				velToKill = 0;
			}
		}
		
		var apparentVel = (this.yvel * speed) / (1000 / 60);
		var velToKill = Math.abs(apparentVel);
		var maxMove = Math.floor(this.height / 2);
		while(velToKill > 0){
			if(velToKill > maxMove){
				this.y += (this.yvel > 0 ? maxMove : -maxMove);
				velToKill -= maxMove;
			}else{
				this.y += (this.yvel > 0 ? velToKill : velToKill * -1);
				velToKill = 0;
			}
			if(this.collide()){
				if(this.setY){
					this.y = this.setY
				}else{
					this.y = ((this.y >> 4) << 4) + (this.yvel > 0 ? 16 - (((this.height - 1) % 16) + 1) : 16);
				}
				if(this.yvel < 0){
					this.down = true;
				}
				this.yvel = this.yvel * this.bounce;
				velToKill = 0;
			}
		}
		
		if(this.collide()){
			this.unstuck(5000);
		}
		
		this.reset();
		
	}
	
	this.unstuck = function(limit){
		var j = 0;
		var originalx = this.x;
		var originaly = this.y;
		while(this.collide() && j < limit){
			this.x = r(originalx + (Math.sin(Math.PI * 2 * ((j % 16) / 16)) * (j / 4)));
			this.y = r(originaly + (Math.cos(Math.PI * 2 * ((j % 16) / 16)) * (j / 4)));
			j++;
		}
		if(j >= limit){
			this.x = originalx;
			this.y = originaly;
			return false;
		}else{
			return true;
		}
	}
	
	this.run = function(){
		this.y += 1;
		if(this.collide() == false && this.gravity){
			this.yvel += (0.025 * gravity) * speed;
		}
		this.y -= 1;
		this.xvel *= Math.pow(this.air[0], speed);
		this.yvel *= Math.pow(this.air[1], speed);
		this.move();
	}
}

// Run game.

function drawLevel(lv){ // Draw level
	for(i = 0; i < lv.length; i++){
		for(j = (lookx > 300 ? r((lookx - 300) / 16) : 0); j < r((lookx + 600) / 16); j++){
			if(lv[i][j] == '#' || lv[i][j] == 'x' || lv[i][j] == 'w'){
				var edgeTile = false;
				if((j < lv[i].length && j > 0 && i < lv.length - 1 && i > 0)){
					var edgeChecks = [[-1, 1], [0, 1], [1, 1], [1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0]];
					for(k in edgeChecks){
						if(lv[i + edgeChecks[k][0]][j + edgeChecks[k][1]] != '#'){
							edgeTile = true;
						}
					}
					if(edgeTile){
						context.fillStyle = '#ddd';
					}else{
						context.fillStyle = '#eee';
					}
				}
				if(lv[i][j] == 'x'){
					context.fillStyle = '#d77';
				}
				if(lv[i][j] == 'w'){
					context.fillStyle = '#47d';
				}
				context.fillRect((j << 4) - r(lookx), i << 4, 16, 16);
			}else if(lv[i][j] == 'E'){
				var enemyToAdd = Math.floor(Math.random() * (lvDis[levelNo] + 0.999));
				if(endAdded || totalEnemies < (level[0].length / 100)){
					switch(enemyToAdd){
						case 0:
							actors[actors.length] = new Actor(8, 1, 80, 5, 80, 1, j << 4, i << 4, 16, 16);
							ais[ais.length] = new Ai(actors.length - 1, 'pace');
							break;
						case 1:
							actors[actors.length] = new Actor(9, 1, 100, 6, 50, 2, j << 4, i << 4, 16, 16);
							ais[ais.length] = new Ai(actors.length - 1, 'alphaBot');
							break;
						case 2:
							actors[actors.length] = new Actor(10, 1, 120, 5, 50, 3, j << 4, i << 4, 16, 16, false, 0);
							ais[ais.length] = new Ai(actors.length - 1, 'fly');
							break;
					}
					totalEnemies += 1;
				}
				level[i] = setStrChar(level[i], j, '.');
			}else if(lv[i][j] == 'B'){
				actors[actors.length] = new Actor(11, 1, 600, 3, 200, 5, j << 4, i << 4, 32, 32);
				ais[ais.length] = new Ai(actors.length - 1, 'boss');
				level[i] = setStrChar(level[i], j, '.');
			}else if(lv[i][j] == 'H'){
				items[items.length] = new Item(0, j << 4, i << 4);
				level[i] = setStrChar(level[i], j, '.');
			}else if(lv[i][j] == 'L'){
				items[items.length] = new Item(1, j << 4, i << 4);
				level[i] = setStrChar(level[i], j, '.');
			}else if(parseInt(lv[i][j]) > -1 && gameMode == 'adventure'){
				if(endAdded || totalLevelScore < (level[0].length / 20) * (levelNo + 2)){
					items[items.length] = new Item(parseInt(lv[i][j]) + 2, j << 4, i << 4);
					totalLevelScore += [5, 10, 20, 50][lv[i][j]];
				}
				level[i] = setStrChar(level[i], j, '.');
			}
		}
	}
}

var speed;
var lastLoop = new Date();
var thisLoop = lastLoop;
function loopGame(){
	canvas.style.margin = (window.innerHeight > 360 ? window.innerHeight / 2 - 180 + 'px auto' : '10px auto');
    lastLoop = thisLoop;
	thisLoop = new Date();
    speed = (thisLoop - lastLoop);
	speed = (speed > 50 ? 50 : speed);
	context.clearRect(0, 0, 500, 350);
	if(endAdded == 1 && lookx >= 20000 + (5000 * levelNo)){
		limitLeft = 20000 + (5000 * levelNo);
		for(var i = 0; i < 20; i++){
			level[i] = setStrChar(level[i], (limitLeft >> 4) - 1, '#');
		}
		endAdded = 2;
	}
	if(game == 'playing'){
		if(gameMode == 'free' || gameMode == 'adventure'){
			var maxx = (actors[0].x >> 4) + 200;
			while(level[0].length < maxx && endAdded == false){
				partIndex = Math.floor(Math.random() * (levelparts.length - 1)) + 1;
				partFound = false;
				if(partsInserted.length == 0){
					var toInsert = levelparts[0];
					partsInserted.push([false, '5n', 1, 1, 0, 0]);
					partFound = true;
				}else if(level[0].length >= (20000 + (5000 * levelNo) >> 4) && gameMode == 'adventure'){
					var toInsert = levelends[levelNo];
					partsInserted.push([false, '5n', 1, 1, 0, 0]);
					partFound = true;
					actors.splice(1, actors.length - 1);
					ais = [];
					limitRight = ((level[0].length << 4) + ((toInsert[0].length) << 4)) - 500;
					endAdded = 1;
				}else{
					thisPart = levelparts[partIndex];
					var prevPart = partsInserted[partsInserted.length - 1];
					if(thisPart[20] == prevPart[1] && (Math.random() * thisPart[24]) < 1 && (prevPart[5] != partIndex || Math.random() <= 0.3)){
						if(partsInserted[partsInserted.length - 1] != thisPart || Math.random() < 0){
							partsInserted.push([thisPart[20], thisPart[21], thisPart[22], thisPart[23], thisPart[24], partIndex]);
							var toInsert = thisPart;
							partFound = true;
						}
					}
				}
				if(partFound){
					for(i = 0; i < 20; i++){
						level[i] += toInsert[i];
					}
				}
			}
		}else if(gameMode == 'time'){
			level = timelevels[levelNo];
			limitRight = (level[0].length << 4) - 516;
		}
		
		if(limitLeft < actors[0].x - 8000){
			limitLeft = actors[0].x - 8000;
		}
	}
	for(i in controllers){
		controllers[i].checkKeys();
	}
	lookx = 0
	looky = 0;
	for(i in camera){
		lookx += (camera[i] instanceof Array ? camera[i][0] : camera[i].x + camera[i].xvel * 1) - 250;
	}
	lookx /= camera.length;
	looky /= camera.length;
	
	if(game == 'playing'){
		if(lookx < limitLeft){
			lookx = limitLeft;
		}
		if(lookx > limitRight && gameMode != 'free'){
			lookx = limitRight;
		}
	}
	
	context.globalAlpha = 1;
	context.lineWidth = 1;
	
	var lv = level;
	drawLevel(lv);
	
	context.fillStyle = 'rgba(255, 200, 200, 0.7)';
	for(i in test){
		context.fillRect((test[i][0] << 4) - lookx, test[i][1] << 4, 16, 16);
	}
	for(i in actors){
		if(actors[i].x < lookx + 550 && actors[i].x > lookx - 50){
			actors[i].draw();
		}
	}
	context.globalAlpha = 1;
	context.fillStyle = "#444";
	context.font = "10pt Arial";
	context.textAlign = 'left';
	if(game == 'playing'){
		context.strokeStyle = '#555';
		context.fillStyle = '#c54';
		context.fillRect(10.5, 285.5, camera[0].health / 5, 10);
		context.strokeRect(10.5, 285.5, camera[0].maxhealth / 5, 10);
		context.fillStyle = '#68f';
		context.fillRect(10.5, 300.5, camera[0].energy / 5, 10);
		context.strokeRect(10.5, 300.5, camera[0].maxenergy / 5, 10);
		for(var l = 0; l < 6; l++){
			context.strokeStyle = (l == actors[0].select ? '#555' : (actors[0].powers > l ? 'rgba(50, 50, 50, 0.5)' : 'rgba(50, 50, 50, 0.2)'));
			context.fillStyle = (l == actors[0].select ? 'rgba(180, 180, 210, 0.5)' : (actors[0].powers > l ? 'rgba(200, 200, 200, 0.2)' : 'rgba(200, 200, 200, 0.1)'));
			context.fillRect(465.5 - (l * 35), 278.5, 30, 30);
			context.strokeRect(465.5 - (l * 35), 278.5, 30,30);
		}
		particles[0].drawIcons();
		context.fillStyle = '#444';
	}else{
		context.fillText('W and S to move', 10, 270);
		context.fillText('Enter to select', 10, 290);
		context.fillText('A and D to change slider value', 10, 310);
	}
	lastspeed = (new Date() % 10 == 0 ? r(1000 / speed) : lastspeed);
	context.fillText('FPS: ' + lastspeed, 10, 20);
	if(game == 'playing' && gameMode == 'adventure'){
		context.fillText('Level: ' + (levelNo + 1), 10, 258);
		context.fillText('Points: ' + score, 70, 275);
		context.fillText('Lives: ' + lives, 10, 275);
	}
	if(game == 'playing'){
		if(gameMode == 'time'){
			var time = r((new Date().getTime() - clockStart));
			if(trialComplete && !message){
				toMenu();
				ui.area = 7;
				ui.select = levelNo;
			}
			context.fillText('Time: ' + (finTime ? finTime : toClock(time, 1)), 10, 40);
		}else{
			trialComplete = false;
		}
	}else{
		trialComplete = false;
	}
	context.textAlign = 'right';
	if(mobile){
		context.fillText('RetX: ' + r(mouse.x), 420, 290);
		context.fillText('RetX: ' + r(mouse.y), 490, 290);
		context.fillText('Sint mobile Beta 1.0', 490, 310);
	}else{
		context.fillText('Sint Beta 1.0', 490, 20); // β
		if(cookies && game == 'menu'){
			context.fillText('Sint uses cookies to remember', 490, 290);
			context.fillText('options and time trial records', 490, 310);
		}
	}
	context.fillText(test, 490, 290);
	for(i in ais){
		if(Math.abs(ais[i].actor.x - lookx) < 2000){
			ais[i].run();
		}
	}
	for(i in actors){
		actors[i].simulate();
	}
	for(i in particles){
		particles[i].simulate();
		particles[i]. draw();
	}
	for(i in items){
		items[i].run();
		items[i].draw();
	}
	
	
	if(message){
		if(message == 'win' && levelNo < 5){
			// Message box
			context.strokeStyle = '#555';
			context.fillStyle = '#ccc';
			context.fillRect(100, 50, 300, 220);
			context.strokeRect(100, 50, 300, 220);
			// Text properties
			context.textAlign = 'center';
			context.font = '12pt Helvetica';
			// Buttons
			context.fillStyle = (ui.select == 3 ? '#9bf' : '#cdf');
			context.fillRect(150, 235, 200, 25); // Continue
			context.fillStyle = (ui.select == 0 ? '#9bf' : '#cdf');
			context.fillRect(110, 135, 280, 25); // Health
			context.fillStyle = (ui.select == 1 ? '#9bf' : '#cdf');
			context.fillRect(110, 165, 280, 25); // Energy
			context.fillStyle = (ui.select == 2 ? '#9bf' : '#cdf');
			context.fillRect(110, 195, 280, 25); // Powers
			// Text
			context.fillStyle = '#fff';
			context.fillText('You have won level ' + (levelNo + 1) + '.', 250, 80);
			context.fillText((5 - levelNo) + ' levels left.', 250, 100);
			context.fillText('Points: ' + score, 250, 125);
			context.fillStyle = (ui.select == 3 ? 'fff' : '#eef');
			context.fillText('Continue', 250, 253);
			context.fillStyle = (score < upgrades.health[upgrades.healthOn] ? (ui.select == 0 ? '#f98' : '#fcd') : (ui.select == 0 ? '#fff' : '#eef'));
			context.fillText('Upgrade health +100: ' + upgrades.health[upgrades.healthOn], 250, 153);
			context.fillStyle = (score < upgrades.energy[upgrades.energyOn] ? (ui.select == 1 ? '#f98' : '#fcd') : (ui.select == 1 ? '#fff' : '#eef'));
			context.fillText('Upgrade max energy +100: ' + upgrades.energy[upgrades.energyOn], 250, 183);
			context.fillStyle = (score < upgrades.powers[actors[0].powers - 1] && actors[0].powers < 8 ? (ui.select == 2 ? '#f98' : '#fcd') : (ui.select == 2 ? '#fff' : '#eef'));
			context.fillText('Unlock next power: ' + upgrades.powers[actors[0].powers - 1], 250, 213);
			
			ui.select = (ui.select + 4) % 4;
			
			if(keysDown.indexOf(87) > -1){
				ui.select -= 1;
			}
			if(keysDown.indexOf(83) > -1){
				ui.select += 1;
			}
			if(keysDown.indexOf(13) > -1){
				switch(ui.select){
					case 0:
						if(score >= upgrades.health[upgrades.healthOn]){
							actors[0].maxhealth += 100;
							actors[0].health += 100;
							score -= upgrades.health[upgrades.healthOn];
							upgrades.healthOn += 1;
						}
						break;
					case 1:
						if(score >= upgrades.energy[upgrades.energyOn]){
							actors[0].maxenergy += 100;
							actors[0].energy += 100;
							score -= upgrades.energy[upgrades.energyOn];
							upgrades.energyOn += 1;
						}
						break;
					case 2:
						if(score > upgrades.powers[actors[0].powers - 1]){
							score -= upgrades.powers[actors[0].powers - 1];
							actors[0].powers += 1;
						}
						break;
					case 3:
						message = false;
						newLevel();
						break;
				}
			}
		}else{
			if(levelNo == 5){
				message = ['You have won the game :D', '', 'Score: ' + score];
			}
			context.strokeStyle = '#555';
			context.fillStyle = '#ccc';
			context.fillRect(100, 100, 300, 120);
			context.strokeRect(100, 100, 300, 120);
			context.textAlign = 'center';
			context.fillStyle = '#9bf';
			context.fillRect(150, 180, 200, 25);
			context.font = '12pt Helvetica';
			context.fillStyle = '#fff';
			context.fillText('Enter to Continue', 250, 198);
			for(j = 0; j < message.length; j++){
				context.fillText(message[j], 250, 130 + (20 * j));
			}
			if(keysDown.indexOf(13) > -1){
				message = false;
				if(lives <= 0 || levelNo == 5){
					toMenu();
				}
			}
		}
	}
	
	if(game == 'menu'){
		if(keysDown.indexOf(83) > -1 && moveLocked == false){
			ui.select += 1;
		}else{
			menudown = false;
		}
		if(keysDown.indexOf(87) > -1 && moveLocked == false){
			ui.select -= 1;
		}
		ui.select = (ui.select + menu[ui.area].length) % menu[ui.area].length;
		if(menu[ui.area][0] == 't'){
			ui.select = 0;
		}
		var thisType = menu[ui.area][ui.select][1];
		// Draw menu
		context.fillStyle = '#69d';
		context.font = '40pt Helvetica';
		context.textAlign = 'center';
		context.shadowColor = '#69d';
		context.shadowBlur = 10;
		context.fillText('Sint', 250, 100);
		context.shadowBlur = 0;
		// Main menu
		if(mobile){
			play();
			game = 'playing';
			gameMode = 'free';
		}
		if(menu[ui.area][0] == 'r'){
			switch(menu[ui.area][1]){
				case 'play':
					play();
					game = 'playing';
					gameMode = 'free';
					levelNo = 19;
					actors[0].powers = 6;
					actors[0].maxhealth = 500;
					actors[0].health = 500;
					actors[0].maxenergy = 500;
					actors[0].energy = 500;
					break;
				case 'time':
					play();
					clockStart = new Date().getTime();
					game = 'playing';
					gameMode = 'time';
					break;
				case 'adventure':
					play();
					var controls = controlChar(optionvars[2]) + ", " + controlChar(optionvars[3]) + " and " + controlChar(optionvars[4]);
					message = ['Use ' + controls + ' to move', 'Mouse to aim and click to shoot', 'Get past level 20 to win'];
					game = 'playing';
					gameMode = 'adventure';
					levelNo = 0;
					break;
				default:
					ui.area = 0;
					break;
			}
		}else if(menu[ui.area][0] == 't'){
					context.fillStyle = '#78a';
					context.font = '12pt Helvetica';
					for(i = 1; i < menu[ui.area].length; i++){
						context.fillText(menu[ui.area][i], 250, 120 + (20 * i));
					}
					context.fillStyle = '#9bf';
					context.fillRect(150, 130 + (20 * i), 200, 25);
					context.font = '12pt Helvetica';
					context.fillStyle = '#fff';
					context.fillText('Back', 250, 148 + (20 * i));
		}else{
			for(i in menu[ui.area]){
				if(menu[ui.area][i][1] == 's'){
					context.fillStyle = (ui.select == i ? '#9bf' : '#cdf');
					context.fillRect(150, 150 + (30 * i), 80, 25);
					context.font = '12pt Helvetica';
					context.fillStyle = (ui.select == i ? '#fff' : '#eef');
					context.fillText(menu[ui.area][i][0], 190, 168 + (30 * i));
					context.fillStyle = '#eee';
					context.strokeStyle = (ui.select == i ? '#cdf' : '#ddf');
					context.fillRect(240, 160 + (30 * i), 110, 5);
					context.strokeRect(240, 160 + (30 * i), 110, 5);
					context.fillStyle = (ui.select == i ? '#9bf' : '#cdf');
					context.strokeStyle = (ui.select == i ? '#79f' : '#abf');
					var thisoption = menu[ui.area][i][2];
					var optionaddx = 100 * (optionvars[thisoption] / (menu[ui.area][i][4] - menu[ui.area][i][3]));
					context.strokeRect(240 + optionaddx, 155 + (30 * i), 10, 10);
					context.fillRect(240 + optionaddx, 155 + (30 * i), 10, 10);
					if(ui.select == i){
						if(keys.indexOf(65) > -1){
							if(optionvars[thisoption] > menu[ui.area][i][3]){
								optionvars[thisoption] -= 0.1 * speed;
							}else{
								optionvars[thisoption] = menu[ui.area][i][3]
							}
						}
						if(keys.indexOf(68) > -1){
							if(optionvars[thisoption] < menu[ui.area][i][4]){
								optionvars[thisoption] += 0.1 * speed;
							}else{
								optionvars[thisoption] = menu[ui.area][i][4]
							}
						}
					}
				}else if(menu[ui.area][i][1] == 'c'){
					context.fillStyle = (ui.select == i ? '#9bf' : '#cdf');
					context.fillRect(150, 120 + (30 * i), 80, 25);
					context.font = '12pt Helvetica';
					context.fillStyle = (ui.select == i ? '#fff' : '#eef');
					context.fillText(menu[ui.area][i][0], 190, 138 + (30 * i));
					context.fillStyle = (ui.select == i ? '#9bf' : '#cdf');
					context.fillRect(240, 120 + (30 * i), 110, 25);
					context.fillStyle = (ui.select == i ? '#fff' : '#eef');
					var tempIn = optionvars[parseInt(i) + 2];
					tempIn = controlChar(tempIn);
					if(tempIn == 0){
						tempIn = 'Enter key';
					}
					context.fillText(tempIn, 290, 138 + (30 * i));
					
					if(ui.select == i){
						if(keysDown.length > 0 && keysDown.indexOf(13) > -1 && moveLocked == false){
							moveLocked = true;
							optionvars[ui.select + 2] = 0;
						}else if(keysDown.length > 0 && moveLocked == true){
							optionvars[ui.select + 2] = keysDown[0];
							moveLocked = false;
						}
					}
				}else{
					thisOne = menu[ui.area];
					if(thisOne[i].length > 3){
						var more = thisOne[i][3];
					}else{
						var more = false;
					}
					context.fillStyle = (ui.select == i ? '#9bf' : '#cdf');
					context.fillRect((more == 'tl' ? 100 : 150), (typeof more === 'number' ? more : 150) + (30 * i), (more == 'tl' ? 300 : 200), 25);
					context.font = '12pt Helvetica';
					context.fillStyle = (ui.select == i ? '#fff' : '#eef');
					context.fillText(menu[ui.area][i][0], 250, (typeof more === 'number' ? more + 18 : 168) + (30 * i));
				}
			}
		}
		if(keysDown.indexOf(13) > -1 && thisType != 'c' && thisType != 's'){
			if(menu[ui.area][ui.select][0] == 't'){
				ui.area = 0;
				ui.select = 0;
			}else if(menu[ui.area][ui.select][3] == 'tl'){
				levelNo = ui.select;
				ui.area = 8;
				ui.select = 0;
			}else{
				ui.area = menu[ui.area][ui.select][1];
				ui.select = 0;
				if(cookies){
					setCookie('options', JSON.stringify(optionvars));
				}
			}
		}
	}
	
	var len = 0;
	if(actors.length > len){
		len = actors.length;
	}
	if(particles.length > len){
		len = particles.length;
	}
	if(items.length > len){
		len = items.length;
	}
	for(i = 0; i < len; i++){
		if(actors.length > i && actors[i].deleteme){
			actors.splice(i, 1);
		}
		if(ais.length > i && ais[i].deleteme){
			ais.splice(i, 1);
		}
		if(particles.length > i && particles[i].deleteme){
			particles.splice(i, 1);
		}
		if(items.length > i && items[i].deleteme){
			items.splice(i, 1);
		}
	}
	
	// Slow down game to test low framerates
	if(slow){
		for(var j=1; j < 10000000; j++){
			j = j;
		}
	}
	if(tomenu){
		toMenu();
	}
	keysDown = [];
	
	 // Play music
	 
	if(game == 'menu'){
		musicToPlay = false;
		musicToPlayID = false;
	}else{
		musicToPlay = music.dash;
		musicToPlayID = 'Dash';
	}
	
	if(musicPlayingID != musicToPlayID){
		if(musicPlaying){
			musicPlaying.sound.pause();
			musicPlaying.sound.currentTime = 0;
		}
		musicStartTime = Math.floor(new Date().getTime() / 1000);
		musicPlaying = musicToPlay;
		musicPlayingID = musicToPlayID;
		if(musicPlaying){
			musicPlaying.sound.play();
		}
	}
	
	if(new Date().getTime() / 1000 > musicStartTime + musicPlaying.len && musicToPlay){
		musicPlaying.sound.play();
	}
	
	if(musicPlaying){
		musicPlaying.sound.volume = optionvars[0] / 100;
	}
}
