var DropEffect = Container.expand(function (parent, x, y) {
	var self = Container.call(this);
	parent.addChild(self);
	self.x = x;
	self.y = y;
	var angle = Math.random() * Math.PI;
	var speed = 10 + Math.random() * 5;
	var speedX = Math.cos(angle) * speed;
	var speedY = Math.sin(angle) * speed;
	var spinSpeed = Math.random() * 0.2 - 0.1;
	var scale = 1;
	var scaleDecrement = 1 / 60;
	var variant = Math.floor(Math.random() * 3);
	var graphics = self.createAsset('pickup' + variant, 'Drop graphics', 0.5, 0.95);
	graphics.rotation = Math.random() * Math.PI * 2;
	self.update = update;
	function update(velocityX, velocityY) {
		self.x += speedX - velocityX;
		self.y += speedY - velocityY;
		scale -= scaleDecrement;
		graphics.rotation += spinSpeed;
		graphics.scale.set(scale);
		return scale <= 0;
	}
});
var BloodsplatterEffect = Container.expand(function (parent, x, y) {
	var self = Container.call(this);
	parent.addChild(self);
	self.x = x;
	self.y = y;
	var duration = 15;
	var remaining = duration;
	var maxScale = 2.0;
	var startAlpha = 0.5;
	var graphics = self.createAsset('bloodsplatter', 'Bloodsplatter graphics', 0.5, 0.5);
	graphics.alpha = startAlpha;
	graphics.y = -graphics.height / 2;
	self.update = update;
	function update(velocityX, velocityY) {
		var progress = 1 - remaining-- / duration;
		if (remaining <= 0) {
			return true;
		}
		graphics.scale.set(1 + (maxScale - 1) * progress);
		graphics.alpha = startAlpha * (1 - progress);
		self.x -= velocityX;
		self.y -= velocityY;
	}
});
var Monster = Container.expand(function (parent, x, y) {
	var self = Container.call(this);
	parent.addChild(self);
	self.x = x;
	self.y = y;
	var active = false;
	var updateTick = 0;
	var updatePeriod = 30;
	var jumpSpeedMax = 30;
	var jumpHeight = 100;
	var jumpVelocityX = 0;
	var jumpVelocityY = 0;
	var jumpDuration = updatePeriod * 2 / 3;
	var invulnerabilityBoost = 1.8;
	var jumpDistMax = jumpSpeedMax * jumpDuration;
	var jumpDistMaxSqr = jumpDistMax * jumpDistMax;
	var activationDist = 1000;
	var activationDistSqr = activationDist * activationDist;
	var despawnY = -2500;
	var shadowReduction = 0.1;
	var shadow = self.createAsset('shadow', 'Monster shadow', 0.45, 1);
	var graphics = self.createAsset('monster', 'Monster graphics', 0.5, 1);
	graphics.scale.x = Math.random() < 0.5 ? 1 : -1;
	shadow.width = graphics.width * 0.9;
	shadow.height = graphics.width * 0.45;
	shadow.tint = 0x000000;
	shadow.alpha = 0.5;
	self.jumping = false;
	self.hitbox = shadow;
	self.update = update;
	function update(velocityX, velocityY, player) {
		if (!active) {
			var dx = player.x - self.x;
			var dy = player.y - self.y;
			var distSqr = dx * dx + dy * dy;
			if (distSqr <= activationDistSqr) {
				active = true;
			}
		}
		if (active) {
			var updateCurve = (-Math.cos(++updateTick * Math.PI / (updatePeriod / 2)) + 0.5) / 1.5;
			if (self.jumping) {
				if (updateCurve <= 0) {
					self.jumping = false;
					jumpVelocityX = 0;
					jumpVelocityY = 0;
				}
			} else {
				if (updateCurve > 0) {
					var boost = player.invulnerable ? invulnerabilityBoost : 1;
					var targetPosX = player.x + velocityX * jumpDuration * boost;
					var targetPosY = player.y + velocityY * jumpDuration * boost;
					var ddx = targetPosX - self.x;
					var ddy = targetPosY - self.y;
					var angle = Math.atan2(ddy, ddx);
					var targetDistSqr = ddx * ddx + ddy * ddy;
					var jumpSpeed = jumpSpeedMax * (targetDistSqr >= jumpDistMaxSqr ? 1 : Math.sqrt(targetDistSqr) / jumpDistMax);
					jumpVelocityX = Math.cos(angle) * jumpSpeed;
					jumpVelocityY = Math.sin(angle) * jumpSpeed;
					self.jumping = true;
				}
			}
			graphics.y = -Math.max(0, updateCurve) * jumpHeight;
			graphics.scale.x = player.x < self.x ? -1 : 1;
			graphics.scale.y = 1 + Math.min(0, updateCurve);
			shadow.scale.set(1 - shadowReduction * updateCurve);
		}
		self.x += jumpVelocityX - velocityX;
		self.y += jumpVelocityY - velocityY;
		return self.y < despawnY;
	}
});
var Pickup = Container.expand(function (parent, x, y) {
	var self = Container.call(this);
	parent.addChild(self);
	self.x = x;
	self.y = y;
	var activeDist = 300;
	var activeDistSqr = activeDist * activeDist;
	var collectDist = 50;
	var collectDistSqr = collectDist * collectDist;
	var collectSpeed = 25;
	var collectOffset = -50;
	var active = false;
	var variant = Math.floor(Math.random() * 3);
	var graphics = self.createAsset('pickup' + variant, 'Pickup graphics', 0.5, 0.95);
	var backShadow = self.createAsset('shadow', 'Pickup background shadow', 0.5, 0.5);
	var foreShadow = self.createAsset('shadow', 'Pickup foreground shadow', 0.5, 0.5);
	foreShadow.width = graphics.width;
	foreShadow.height = graphics.width * 0.25;
	backShadow.width = graphics.width;
	backShadow.height = graphics.width * 0.25;
	backShadow.tint = 0x000000;
	backShadow.y = -3;
	self.update = update;
	function update(velocityX, velocityY, player) {
		var dx = player.x - self.x;
		var dy = player.y + collectOffset - self.y;
		var distSqr = dx * dx + dy * dy;
		if (self.active) {
			var dist = Math.sqrt(distSqr);
			self.x += dx / dist * collectSpeed;
			self.y += dy / dist * collectSpeed;
		} else if (distSqr <= activeDistSqr) {
			self.active = true;
			backShadow.destroy();
			foreShadow.destroy();
		} else {
			self.x -= velocityX;
			self.y -= velocityY;
		}
		return distSqr <= collectDistSqr;
	}
});
var LandscapeTile = Container.expand(function (parent, x, y, {lookup, xIndex, yIndex, width, height, density, pickups, monsters, player, key, landscapeTiles}) {
	var self = Container.call(this);
	parent.addChild(self);
	self.x = x;
	self.y = y;
	var pickupChance = 0.1;
	var monsterChance = 0.01;
	var distanceFactor = 10000;
	var monsterDistance = 20000;
	var obstructions = [];
	var visual = self.createAsset('hitbox', 'Landscape area', 0.5, 0.5);
	visual.alpha = 0;
	visual.width = width;
	visual.height = height;
	self.activate = activate;
	self.width = width;
	self.height = height;
	self.active = false;
	self.key = key;
	self.checkCollision = checkCollision;
	function checkCollision(player) {
		var reach = 100;
		var dx = Math.abs(player.x - self.x);
		var dy = Math.abs(player.y - self.y);
		if (dx < width / 2 + reach && dy < height / 2 + reach) {
			for (var i = 0; i < obstructions.length; i++) {
				var obstruction = obstructions[i];
				if (obstruction.active && player.hitbox.intersects(obstruction.hitbox)) {
					obstruction.deactivate();
					return true;
				}
			}
		}
		return false;
	}
	function activate() {
		if (!self.active) {
			self.active = true;
			var densityChance = density * Math.random();
			var obstructionTypes = ['largeTree', 'smallTree', 'smallTree', 'deadTree', 'smallRock', 'smallRock', 'largeRock', 'stump'];
			var clearanceDist = 150;
			var clearanceDistSqr = clearanceDist * clearanceDist;
			var spawnBorder = 25;
			var cellSize = 150;
			var cols = Math.ceil(width / cellSize);
			var rows = Math.ceil(height / cellSize);
			var cellWidth = width / cols;
			var cellHeight = height / rows;
			var pickup;
			if (Math.random() <= 0.1) {
				var {pointX, pointY} = randomPointInRect(self.x, self.y, width, height, clearanceDist);
				pickups.push(pickup = new Pickup(parent, pointX, pointY));
			}
			if (player.distanceTravelled > monsterDistance && Math.random() <= monsterChance) {
				var {pointX, pointY} = randomPointInRect(self.x, self.y, width, height, clearanceDist);
				monsters.push(new Monster(parent, self.x, self.y));
			}
			for (i = 0; i < cols; i++) {
				for (j = 0; j < rows; j++) {
					if (Math.random() <= densityChance) {
						var canPlace = true;
						var {pointX, pointY} = randomPointInRect(cellWidth * i, cellHeight * j, cellWidth, cellHeight, spawnBorder);
						if (pickup) {
							var dx = pickup.x - pointX;
							var dy = pickup.y - pointY;
							var distSqr = dx * dx + dy * dy;
							if (distSqr <= clearanceDistSqr) {
								canPlace = false;
							}
						}
						if (canPlace) {
							var type = obstructionTypes[Math.floor(Math.random() * obstructionTypes.length)];
							obstructions.push(new Obstruction(self, pointX, pointY, type));
						}
					}
				}
			}
		}
		var leftKey = xIndex - 1 + ':' + yIndex;
		var rightKey = xIndex + 1 + ':' + yIndex;
		var downKey = xIndex + ':' + (yIndex + 1);
		var newDensity = (1 + Math.pow(Math.log(player.distanceTravelled / distanceFactor + 2), 2)) / 100;
		if (!lookup[leftKey]) {
			landscapeTiles.push(lookup[leftKey] = new LandscapeTile(parent, self.x - width, self.y, {
				key: leftKey,
				density: newDensity,
				xIndex: xIndex - 1,
				yIndex,
				landscapeTiles,
				width,
				height,
				lookup,
				pickups,
				monsters,
				player
			}));
		}
		if (!lookup[rightKey]) {
			landscapeTiles.push(lookup[rightKey] = new LandscapeTile(parent, self.x + width, self.y, {
				key: rightKey,
				density: newDensity,
				xIndex: xIndex + 1,
				yIndex,
				landscapeTiles,
				width,
				height,
				lookup,
				pickups,
				monsters,
				player
			}));
		}
		if (!lookup[downKey]) {
			landscapeTiles.push(lookup[downKey] = new LandscapeTile(parent, self.x, self.y + height, {
				key: downKey,
				density: newDensity,
				yIndex: yIndex + 1,
				xIndex,
				landscapeTiles,
				width,
				height,
				lookup,
				pickups,
				monsters,
				player
			}));
		}
		function randomPointInRect(centerX, centerY, width, height, border = 0) {
			var startX = centerX - width / 2 + border;
			var startY = centerY - height / 2 + border;
			var innerWidth = width - border * 2;
			var innerHeight = height - border * 2;
			var pointX = startX + Math.random() * innerWidth;
			var pointY = startY + Math.random() * innerHeight;
			return {
				pointX,
				pointY
			};
		}
	}
});
var Obstruction = Container.expand(function (parent, x, y, type) {
	var self = Container.call(this);
	parent.addChild(self);
	self.x = x;
	self.y = y;
	var graphics = self.createAsset(type, 'Obstruction graphics', 0.5, 1);
	var hitbox = self.createAsset('hitbox', 'Obstruction hitbox', 0.5, 0.5);
	var randomScale = 0.9 + Math.random() * 0.2;
	hitbox.y = -25;
	hitbox.alpha = 0;
	hitbox.width = graphics.width * 0.8;
	hitbox.height = graphics.width * 0.45;
	self.scale.set(randomScale, randomScale);
	self.active = true;
	self.hitbox = hitbox;
	self.deactivate = deactivate;
	function deactivate() {
		self.active = false;
		graphics.alpha = 0.5;
	}
});
var Player = Container.expand(function (parent, x, y) {
	var self = Container.call(this);
	parent.addChild(self);
	self.x = x;
	self.y = y;
	var speed = 0;
	var tiltSpeedFactor = 100;
	var platformGraphics = self.createAsset('platform', 'Platform image', 0.4, 0.5);
	var playerGraphics = self.createAsset('player', 'Player character', 0.575, 1);
	var hitbox = self.createAsset('hitbox', 'Player Hitbox', 0.5, 0.5);
	hitbox.width = 25;
	hitbox.height = 25;
	hitbox.alpha = 0;
	self.invulnerable = false;
	self.invulnerableTime = 3 * 60;
	self.invulnerableTimer = 0;
	self.distanceTravelled = 0;
	self.hitbox = hitbox;
	self.update = update;
	function update(targetPosition) {
		if (self.invulnerable) {
			if (--self.invulnerableTimer <= 0) {
				self.invulnerable = false;
			}
			self.alpha = self.invulnerableTimer % 60 < 30 ? 1 : 0.5;
		}
		var dx = targetPosition.x - self.x;
		var dy = targetPosition.y - self.y;
		var angle = angleClamp(Math.atan2(dy, dx));
		var acceleration = (Math.sin(angle) - 0.1) * 2.0;
		var reduction = self.invulnerable ? 1 - self.invulnerableTimer / self.invulnerableTime : 1;
		speed = Math.max(0, speed * 0.95 + acceleration);
		var velocityX = Math.cos(angle) * reduction * speed;
		var velocityY = Math.sin(angle) * reduction * speed;
		var facingSide = targetPosition.x < self.x ? -1 : 1;
		self.distanceTravelled += speed;
		platformGraphics.rotation = angle;
		playerGraphics.rotation = speed * reduction / tiltSpeedFactor * (Math.PI / 2) * facingSide;
		playerGraphics.scale.x = facingSide;
		return {
			velocityX,
			velocityY
		};
	}
	function angleClamp(angle) {
		return angle >= 0 ? angle : angle < -Math.PI / 2 ? Math.PI : 0;
	}
});
var Trail = Container.expand(function (parent, x, y) {
	var self = Container.call(this);
	parent.addChild(self);
	self.x = x;
	self.y = y;
	var trailLength = 60;
	var trailAlpha = 0.4;
	var trailElements = [];
	self.update = update;
	function update(velocityX, velocityY) {
		var trailElement = null;
		if (velocityX !== 0 || velocityY !== 0) {
			trailElement = self.createAsset('trail', 'Trail element', 0, 0.5);
			var angle = Math.atan2(velocityY, velocityX);
			var speed = Math.sqrt(velocityX * velocityX + velocityY * velocityY);
			trailElement.rotation = angle;
			trailElement.scale.x = speed / 100;
		}
		trailElements.push(trailElement);
		if (trailElements.length > trailLength) {
			var removedElement = trailElements.shift();
			if (removedElement) {
				removedElement.destroy();
			}
		}
		for (var i = trailElements.length - 1; i >= 0; i--) {
			var element = trailElements[i];
			if (element) {
				var alpha = trailAlpha * (i / trailLength);
				element.x -= velocityX;
				element.y -= velocityY;
				element.alpha = alpha;
			}
		}
	}
});
var Interface = Container.expand(function (parent, x, y) {
	var self = Container.call(this);
	parent.addChild(self);
	self.x = x;
	self.y = y;
	var score = 0;
	var lives = 3;
	var lifeIcons = [];
	var scoreText = new Text2('| ' + score, {
		size: 70,
		fill: "#000000",
		align: 'left',
		font: "'Press Start 2P', monospace"
	});
	self.addChild(scoreText);
	for (var i = 0; i < lives; i++) {
		var lifeIcon = self.createAsset('lifeIcon', 'Life icon', 1, 0);
		lifeIcon.x = -30 - i * 80;
		lifeIcon.y = scoreText.height - 60;
		lifeIcons.push(lifeIcon);
	}
	scoreText.x = -5;
	scoreText.y = 0;
	scoreText.anchor.set(0, 0);
	self.changeScore = changeScore;
	self.changeLives = changeLives;
	function changeLives(amount) {
		lives += amount;
		for (var i = 0; i < lifeIcons.length; i++) {
			lifeIcons[i].alpha = i < lives ? 1 : 0.5;
		}
		if (amount < 0) {
			LK.effects.flashScreen(0xaa0000, 300);
			if (lives <= 0) {
				return true;
			}
		}
		return false;
	}
	function changeScore(amount) {
		var returnValue = amount < 0 && score > 0;
		score = Math.max(0, score + amount);
		LK.setScore(score);
		scoreText.setText('| ' + score);
		return returnValue;
	}
});
var Game = Container.expand(function () {
	var self = Container.call(this);
	var stageWidth = 2048;
	var stageHeight = 2732;
	var tileSize = 512;
	var playerPosX = Math.round(stageWidth / 2);
	var playerPosY = Math.round(stageWidth / 3);
	var tileMargin = tileSize;
	var background = self.createAsset('background', 'Fullscreen background', 0, 0);
	var landscapeLookup = {};
	var landscapeTiles = [];
	var pickups = [];
	var monsters = [];
	var effects = [];
	var speed = 0;
	background.width = stageWidth;
	background.height = stageHeight;
	var interface = new Interface(self, 0, 10);
	LK.gui.topCenter.addChild(interface);
	var trail = new Trail(self, playerPosX, playerPosY);
	var player = new Player(self, playerPosX, playerPosY);
	landscapeTiles.push(landscapeLookup['0:0'] = new LandscapeTile(self, player.x, player.y, {
		width: tileSize,
		height: tileSize,
		density: -1,
		lookup: landscapeLookup,
		xIndex: 0,
		yIndex: 0,
		key: '0:0',
		landscapeTiles,
		monsters,
		pickups,
		player
	}));
	var isMouseDown = false;
	var targetPosition = {
		x: playerPosX,
		y: playerPosY
	};
	stage.on('down', function (obj) {
		isMouseDown = true;
		updatePosition(obj.event);
	});
	stage.on('up', function (obj) {
		isMouseDown = false;
	});
	stage.on('move', function (obj) {
		if (isMouseDown) {
			updatePosition(obj.event);
		}
	});
	LK.on('tick', function () {
		var {velocityX, velocityY} = player.update(targetPosition);
		trail.update(velocityX, velocityY);
		for (var i = landscapeTiles.length - 1; i >= 0; i--) {
			var landscapeTile = landscapeTiles[i];
			landscapeTile.x -= velocityX;
			landscapeTile.y -= velocityY;
			if (landscapeTile.y < -(tileSize / 2 + tileMargin)) {
				landscapeTile.destroy();
				landscapeTiles.splice(i, 1);
				landscapeLookup[landscapeTile.key] = undefined;
			} else if (!landscapeTile.active && landscapeTile.x > -tileMargin && landscapeTile.x < stageWidth + tileMargin && landscapeTile.y < stageHeight + tileMargin) {
				landscapeTile.activate();
			} else if (landscapeTile.checkCollision(player)) {
				if (interface.changeScore(-1)) {
					effects.push(new DropEffect(self, player.x, player.y));
				}
				if (!player.invulnerable) {
					player.invulnerable = true;
					player.invulnerableTimer = player.invulnerableTime;
				}
			}
		}
		for (var i = pickups.length - 1; i >= 0; i--) {
			var pickup = pickups[i];
			if (pickup.update(velocityX, velocityY, player)) {
				interface.changeScore(1);
				pickup.destroy();
				pickups.splice(i, 1);
			} else if (pickup.y < -(tileSize / 2 + tileMargin) || pickup.active && player.hitbox.intersects(pickup.hitbox)) {
				pickup.destroy();
				pickups.splice(i, 1);
			}
		}
		for (var i = effects.length - 1; i >= 0; i--) {
			var effect = effects[i];
			if (effect.update(velocityX, velocityY)) {
				effect.destroy();
				effects.splice(i, 1);
			}
		}
		for (var i = monsters.length - 1; i >= 0; i--) {
			var monster = monsters[i];
			if (monster.update(velocityX, velocityY, player)) {
				monster.destroy();
				monsters.splice(i, 1);
			} else if (!monster.jumping && monster.hitbox.intersects(player.hitbox)) {
				effects.push(new BloodsplatterEffect(self, monster.x, monster.y));
				monster.destroy();
				monsters.splice(i, 1);
				if (interface.changeLives(-1)) {
					LK.showGameOver();
				}
			}
		}
	});
	function updatePosition(event) {
		var pos = event.getLocalPosition(self);
		targetPosition.x = pos.x;
		targetPosition.y = pos.y;
	}
});
 var DropEffect = Container.expand(function (parent, x, y) {
	var self = Container.call(this);
	parent.addChild(self);
	self.x = x;
	self.y = y;
	var angle = Math.random() * Math.PI;
	var speed = 10 + Math.random() * 5;
	var speedX = Math.cos(angle) * speed;
	var speedY = Math.sin(angle) * speed;
	var spinSpeed = Math.random() * 0.2 - 0.1;
	var scale = 1;
	var scaleDecrement = 1 / 60;
	var variant = Math.floor(Math.random() * 3);
	var graphics = self.createAsset('pickup' + variant, 'Drop graphics', 0.5, 0.95);
	graphics.rotation = Math.random() * Math.PI * 2;
	self.update = update;
	function update(velocityX, velocityY) {
		self.x += speedX - velocityX;
		self.y += speedY - velocityY;
		scale -= scaleDecrement;
		graphics.rotation += spinSpeed;
		graphics.scale.set(scale);
		return scale <= 0;
	}
});
var BloodsplatterEffect = Container.expand(function (parent, x, y) {
	var self = Container.call(this);
	parent.addChild(self);
	self.x = x;
	self.y = y;
	var duration = 15;
	var remaining = duration;
	var maxScale = 2.0;
	var startAlpha = 0.5;
	var graphics = self.createAsset('bloodsplatter', 'Bloodsplatter graphics', 0.5, 0.5);
	graphics.alpha = startAlpha;
	graphics.y = -graphics.height / 2;
	self.update = update;
	function update(velocityX, velocityY) {
		var progress = 1 - remaining-- / duration;
		if (remaining <= 0) {
			return true;
		}
		graphics.scale.set(1 + (maxScale - 1) * progress);
		graphics.alpha = startAlpha * (1 - progress);
		self.x -= velocityX;
		self.y -= velocityY;
	}
});
var Monster = Container.expand(function (parent, x, y) {
	var self = Container.call(this);
	parent.addChild(self);
	self.x = x;
	self.y = y;
	var active = false;
	var updateTick = 0;
	var updatePeriod = 30;
	var jumpSpeedMax = 30;
	var jumpHeight = 100;
	var jumpVelocityX = 0;
	var jumpVelocityY = 0;
	var jumpDuration = updatePeriod * 2 / 3;
	var invulnerabilityBoost = 1.8;
	var jumpDistMax = jumpSpeedMax * jumpDuration;
	var jumpDistMaxSqr = jumpDistMax * jumpDistMax;
	var activationDist = 1000;
	var activationDistSqr = activationDist * activationDist;
	var despawnY = -2500;
	var shadowReduction = 0.1;
	var shadow = self.createAsset('shadow', 'Monster shadow', 0.45, 1);
	var graphics = self.createAsset('monster', 'Monster graphics', 0.5, 1);
	graphics.scale.x = Math.random() < 0.5 ? 1 : -1;
	shadow.width = graphics.width * 0.9;
	shadow.height = graphics.width * 0.45;
	shadow.tint = 0x000000;
	shadow.alpha = 0.5;
	self.jumping = false;
	self.hitbox = shadow;
	self.update = update;
	function update(velocityX, velocityY, player) {
		if (!active) {
			var dx = player.x - self.x;
			var dy = player.y - self.y;
			var distSqr = dx * dx + dy * dy;
			if (distSqr <= activationDistSqr) {
				active = true;
			}
		}
		if (active) {
			var updateCurve = (-Math.cos(++updateTick * Math.PI / (updatePeriod / 2)) + 0.5) / 1.5;
			if (self.jumping) {
				if (updateCurve <= 0) {
					self.jumping = false;
					jumpVelocityX = 0;
					jumpVelocityY = 0;
				}
			} else {
				if (updateCurve > 0) {
					var boost = player.invulnerable ? invulnerabilityBoost : 1;
					var targetPosX = player.x + velocityX * jumpDuration * boost;
					var targetPosY = player.y + velocityY * jumpDuration * boost;
					var ddx = targetPosX - self.x;
					var ddy = targetPosY - self.y;
					var angle = Math.atan2(ddy, ddx);
					var targetDistSqr = ddx * ddx + ddy * ddy;
					var jumpSpeed = jumpSpeedMax * (targetDistSqr >= jumpDistMaxSqr ? 1 : Math.sqrt(targetDistSqr) / jumpDistMax);
					jumpVelocityX = Math.cos(angle) * jumpSpeed;
					jumpVelocityY = Math.sin(angle) * jumpSpeed;
					self.jumping = true;
				}
			}
			graphics.y = -Math.max(0, updateCurve) * jumpHeight;
			graphics.scale.x = player.x < self.x ? -1 : 1;
			graphics.scale.y = 1 + Math.min(0, updateCurve);
			shadow.scale.set(1 - shadowReduction * updateCurve);
		}
		self.x += jumpVelocityX - velocityX;
		self.y += jumpVelocityY - velocityY;
		return self.y < despawnY;
	}
});
var Pickup = Container.expand(function (parent, x, y) {
	var self = Container.call(this);
	parent.addChild(self);
	self.x = x;
	self.y = y;
	var activeDist = 300;
	var activeDistSqr = activeDist * activeDist;
	var collectDist = 50;
	var collectDistSqr = collectDist * collectDist;
	var collectSpeed = 25;
	var collectOffset = -50;
	var active = false;
	var variant = Math.floor(Math.random() * 3);
	var graphics = self.createAsset('pickup' + variant, 'Pickup graphics', 0.5, 0.95);
	var backShadow = self.createAsset('shadow', 'Pickup background shadow', 0.5, 0.5);
	var foreShadow = self.createAsset('shadow', 'Pickup foreground shadow', 0.5, 0.5);
	foreShadow.width = graphics.width;
	foreShadow.height = graphics.width * 0.25;
	backShadow.width = graphics.width;
	backShadow.height = graphics.width * 0.25;
	backShadow.tint = 0x000000;
	backShadow.y = -3;
	self.update = update;
	function update(velocityX, velocityY, player) {
		var dx = player.x - self.x;
		var dy = player.y + collectOffset - self.y;
		var distSqr = dx * dx + dy * dy;
		if (self.active) {
			var dist = Math.sqrt(distSqr);
			self.x += dx / dist * collectSpeed;
			self.y += dy / dist * collectSpeed;
		} else if (distSqr <= activeDistSqr) {
			self.active = true;
			backShadow.destroy();
			foreShadow.destroy();
		} else {
			self.x -= velocityX;
			self.y -= velocityY;
		}
		return distSqr <= collectDistSqr;
	}
});
var LandscapeTile = Container.expand(function (parent, x, y, {lookup, xIndex, yIndex, width, height, density, pickups, monsters, player, key, landscapeTiles}) {
	var self = Container.call(this);
	parent.addChild(self);
	self.x = x;
	self.y = y;
	var pickupChance = 0.1;
	var monsterChance = 0.01;
	var distanceFactor = 10000;
	var monsterDistance = 20000;
	var obstructions = [];
	var visual = self.createAsset('hitbox', 'Landscape area', 0.5, 0.5);
	visual.alpha = 0;
	visual.width = width;
	visual.height = height;
	self.activate = activate;
	self.width = width;
	self.height = height;
	self.active = false;
	self.key = key;
	self.checkCollision = checkCollision;
	function checkCollision(player) {
		var reach = 100;
		var dx = Math.abs(player.x - self.x);
		var dy = Math.abs(player.y - self.y);
		if (dx < width / 2 + reach && dy < height / 2 + reach) {
			for (var i = 0; i < obstructions.length; i++) {
				var obstruction = obstructions[i];
				if (obstruction.active && player.hitbox.intersects(obstruction.hitbox)) {
					obstruction.deactivate();
					return true;
				}
			}
		}
		return false;
	}
	function activate() {
		if (!self.active) {
			self.active = true;
			var densityChance = density * Math.random();
			var obstructionTypes = ['largeTree', 'smallTree', 'smallTree', 'deadTree', 'smallRock', 'smallRock', 'largeRock', 'stump'];
			var clearanceDist = 150;
			var clearanceDistSqr = clearanceDist * clearanceDist;
			var spawnBorder = 25;
			var cellSize = 150;
			var cols = Math.ceil(width / cellSize);
			var rows = Math.ceil(height / cellSize);
			var cellWidth = width / cols;
			var cellHeight = height / rows;
			var pickup;
			if (Math.random() <= 0.1) {
				var {pointX, pointY} = randomPointInRect(self.x, self.y, width, height, clearanceDist);
				pickups.push(pickup = new Pickup(parent, pointX, pointY));
			}
			if (player.distanceTravelled > monsterDistance && Math.random() <= monsterChance) {
				var {pointX, pointY} = randomPointInRect(self.x, self.y, width, height, clearanceDist);
				monsters.push(new Monster(parent, self.x, self.y));
			}
			for (i = 0; i < cols; i++) {
				for (j = 0; j < rows; j++) {
					if (Math.random() <= densityChance) {
						var canPlace = true;
						var {pointX, pointY} = randomPointInRect(cellWidth * i, cellHeight * j, cellWidth, cellHeight, spawnBorder);
						if (pickup) {
							var dx = pickup.x - pointX;
							var dy = pickup.y - pointY;
							var distSqr = dx * dx + dy * dy;
							if (distSqr <= clearanceDistSqr) {
								canPlace = false;
							}
						}
						if (canPlace) {
							var type = obstructionTypes[Math.floor(Math.random() * obstructionTypes.length)];
							obstructions.push(new Obstruction(self, pointX, pointY, type));
						}
					}
				}
			}
		}
		var leftKey = xIndex - 1 + ':' + yIndex;
		var rightKey = xIndex + 1 + ':' + yIndex;
		var downKey = xIndex + ':' + (yIndex + 1);
		var newDensity = (1 + Math.pow(Math.log(player.distanceTravelled / distanceFactor + 2), 2)) / 100;
		if (!lookup[leftKey]) {
			landscapeTiles.push(lookup[leftKey] = new LandscapeTile(parent, self.x - width, self.y, {
				key: leftKey,
				density: newDensity,
				xIndex: xIndex - 1,
				yIndex,
				landscapeTiles,
				width,
				height,
				lookup,
				pickups,
				monsters,
				player
			}));
		}
		if (!lookup[rightKey]) {
			landscapeTiles.push(lookup[rightKey] = new LandscapeTile(parent, self.x + width, self.y, {
				key: rightKey,
				density: newDensity,
				xIndex: xIndex + 1,
				yIndex,
				landscapeTiles,
				width,
				height,
				lookup,
				pickups,
				monsters,
				player
			}));
		}
		if (!lookup[downKey]) {
			landscapeTiles.push(lookup[downKey] = new LandscapeTile(parent, self.x, self.y + height, {
				key: downKey,
				density: newDensity,
				yIndex: yIndex + 1,
				xIndex,
				landscapeTiles,
				width,
				height,
				lookup,
				pickups,
				monsters,
				player
			}));
		}
		function randomPointInRect(centerX, centerY, width, height, border = 0) {
			var startX = centerX - width / 2 + border;
			var startY = centerY - height / 2 + border;
			var innerWidth = width - border * 2;
			var innerHeight = height - border * 2;
			var pointX = startX + Math.random() * innerWidth;
			var pointY = startY + Math.random() * innerHeight;
			return {
				pointX,
				pointY
			};
		}
	}
});
var Obstruction = Container.expand(function (parent, x, y, type) {
	var self = Container.call(this);
	parent.addChild(self);
	self.x = x;
	self.y = y;
	var graphics = self.createAsset(type, 'Obstruction graphics', 0.5, 1);
	var hitbox = self.createAsset('hitbox', 'Obstruction hitbox', 0.5, 0.5);
	var randomScale = 0.9 + Math.random() * 0.2;
	hitbox.y = -25;
	hitbox.alpha = 0;
	hitbox.width = graphics.width * 0.8;
	hitbox.height = graphics.width * 0.45;
	self.scale.set(randomScale, randomScale);
	self.active = true;
	self.hitbox = hitbox;
	self.deactivate = deactivate;
	function deactivate() {
		self.active = false;
		graphics.alpha = 0.5;
	}
});
var Player = Container.expand(function (parent, x, y) {
	var self = Container.call(this);
	parent.addChild(self);
	self.x = x;
	self.y = y;
	var speed = 0;
	var tiltSpeedFactor = 100;
	var platformGraphics = self.createAsset('platform', 'Platform image', 0.4, 0.5);
	var playerGraphics = self.createAsset('player', 'Player character', 0.575, 1);
	var hitbox = self.createAsset('hitbox', 'Player Hitbox', 0.5, 0.5);
	hitbox.width = 25;
	hitbox.height = 25;
	hitbox.alpha = 0;
	self.invulnerable = false;
	self.invulnerableTime = 3 * 60;
	self.invulnerableTimer = 0;
	self.distanceTravelled = 0;
	self.hitbox = hitbox;
	self.update = update;
	function update(targetPosition) {
		if (self.invulnerable) {
			if (--self.invulnerableTimer <= 0) {
				self.invulnerable = false;
			}
			self.alpha = self.invulnerableTimer % 60 < 30 ? 1 : 0.5;
		}
		var dx = targetPosition.x - self.x;
		var dy = targetPosition.y - self.y;
		var angle = angleClamp(Math.atan2(dy, dx));
		var acceleration = (Math.sin(angle) - 0.1) * 2.0;
		var reduction = self.invulnerable ? 1 - self.invulnerableTimer / self.invulnerableTime : 1;
		speed = Math.max(0, speed * 0.95 + acceleration);
		var velocityX = Math.cos(angle) * reduction * speed;
		var velocityY = Math.sin(angle) * reduction * speed;
		var facingSide = targetPosition.x < self.x ? -1 : 1;
		self.distanceTravelled += speed;
		platformGraphics.rotation = angle;
		playerGraphics.rotation = speed * reduction / tiltSpeedFactor * (Math.PI / 2) * facingSide;
		playerGraphics.scale.x = facingSide;
		return {
			velocityX,
			velocityY
		};
	}
	function angleClamp(angle) {
		return angle >= 0 ? angle : angle < -Math.PI / 2 ? Math.PI : 0;
	}
});
var Trail = Container.expand(function (parent, x, y) {
	var self = Container.call(this);
	parent.addChild(self);
	self.x = x;
	self.y = y;
	var trailLength = 60;
	var trailAlpha = 0.4;
	var trailElements = [];
	self.update = update;
	function update(velocityX, velocityY) {
		var trailElement = null;
		if (velocityX !== 0 || velocityY !== 0) {
			trailElement = self.createAsset('trail', 'Trail element', 0, 0.5);
			var angle = Math.atan2(velocityY, velocityX);
			var speed = Math.sqrt(velocityX * velocityX + velocityY * velocityY);
			trailElement.rotation = angle;
			trailElement.scale.x = speed / 100;
		}
		trailElements.push(trailElement);
		if (trailElements.length > trailLength) {
			var removedElement = trailElements.shift();
			if (removedElement) {
				removedElement.destroy();
			}
		}
		for (var i = trailElements.length - 1; i >= 0; i--) {
			var element = trailElements[i];
			if (element) {
				var alpha = trailAlpha * (i / trailLength);
				element.x -= velocityX;
				element.y -= velocityY;
				element.alpha = alpha;
			}
		}
	}
});
var Interface = Container.expand(function (parent, x, y) {
	var self = Container.call(this);
	parent.addChild(self);
	self.x = x;
	self.y = y;
	var score = 0;
	var lives = 3;
	var lifeIcons = [];
	var scoreText = new Text2('| ' + score, {
		size: 70,
		fill: "#000000",
		align: 'left',
		font: "'Press Start 2P', monospace"
	});
	self.addChild(scoreText);
	for (var i = 0; i < lives; i++) {
		var lifeIcon = self.createAsset('lifeIcon', 'Life icon', 1, 0);
		lifeIcon.x = -30 - i * 80;
		lifeIcon.y = scoreText.height - 60;
		lifeIcons.push(lifeIcon);
	}
	scoreText.x = -5;
	scoreText.y = 0;
	scoreText.anchor.set(0, 0);
	self.changeScore = changeScore;
	self.changeLives = changeLives;
	function changeLives(amount) {
		lives += amount;
		for (var i = 0; i < lifeIcons.length; i++) {
			lifeIcons[i].alpha = i < lives ? 1 : 0.5;
		}
		if (amount < 0) {
			LK.effects.flashScreen(0xaa0000, 300);
			if (lives <= 0) {
				return true;
			}
		}
		return false;
	}
	function changeScore(amount) {
		var returnValue = amount < 0 && score > 0;
		score = Math.max(0, score + amount);
		LK.setScore(score);
		scoreText.setText('| ' + score);
		return returnValue;
	}
});
var Game = Container.expand(function () {
	var self = Container.call(this);
	var stageWidth = 2048;
	var stageHeight = 2732;
	var tileSize = 512;
	var playerPosX = Math.round(stageWidth / 2);
	var playerPosY = Math.round(stageWidth / 3);
	var tileMargin = tileSize;
	var background = self.createAsset('background', 'Fullscreen background', 0, 0);
	var landscapeLookup = {};
	var landscapeTiles = [];
	var pickups = [];
	var monsters = [];
	var effects = [];
	var speed = 0;
	background.width = stageWidth;
	background.height = stageHeight;
	var interface = new Interface(self, 0, 10);
	LK.gui.topCenter.addChild(interface);
	var trail = new Trail(self, playerPosX, playerPosY);
	var player = new Player(self, playerPosX, playerPosY);
	landscapeTiles.push(landscapeLookup['0:0'] = new LandscapeTile(self, player.x, player.y, {
		width: tileSize,
		height: tileSize,
		density: -1,
		lookup: landscapeLookup,
		xIndex: 0,
		yIndex: 0,
		key: '0:0',
		landscapeTiles,
		monsters,
		pickups,
		player
	}));
	var isMouseDown = false;
	var targetPosition = {
		x: playerPosX,
		y: playerPosY
	};
	stage.on('down', function (obj) {
		isMouseDown = true;
		updatePosition(obj.event);
	});
	stage.on('up', function (obj) {
		isMouseDown = false;
	});
	stage.on('move', function (obj) {
		if (isMouseDown) {
			updatePosition(obj.event);
		}
	});
	LK.on('tick', function () {
		var {velocityX, velocityY} = player.update(targetPosition);
		trail.update(velocityX, velocityY);
		for (var i = landscapeTiles.length - 1; i >= 0; i--) {
			var landscapeTile = landscapeTiles[i];
			landscapeTile.x -= velocityX;
			landscapeTile.y -= velocityY;
			if (landscapeTile.y < -(tileSize / 2 + tileMargin)) {
				landscapeTile.destroy();
				landscapeTiles.splice(i, 1);
				landscapeLookup[landscapeTile.key] = undefined;
			} else if (!landscapeTile.active && landscapeTile.x > -tileMargin && landscapeTile.x < stageWidth + tileMargin && landscapeTile.y < stageHeight + tileMargin) {
				landscapeTile.activate();
			} else if (landscapeTile.checkCollision(player)) {
				if (interface.changeScore(-1)) {
					effects.push(new DropEffect(self, player.x, player.y));
				}
				if (!player.invulnerable) {
					player.invulnerable = true;
					player.invulnerableTimer = player.invulnerableTime;
				}
			}
		}
		for (var i = pickups.length - 1; i >= 0; i--) {
			var pickup = pickups[i];
			if (pickup.update(velocityX, velocityY, player)) {
				interface.changeScore(1);
				pickup.destroy();
				pickups.splice(i, 1);
			} else if (pickup.y < -(tileSize / 2 + tileMargin) || pickup.active && player.hitbox.intersects(pickup.hitbox)) {
				pickup.destroy();
				pickups.splice(i, 1);
			}
		}
		for (var i = effects.length - 1; i >= 0; i--) {
			var effect = effects[i];
			if (effect.update(velocityX, velocityY)) {
				effect.destroy();
				effects.splice(i, 1);
			}
		}
		for (var i = monsters.length - 1; i >= 0; i--) {
			var monster = monsters[i];
			if (monster.update(velocityX, velocityY, player)) {
				monster.destroy();
				monsters.splice(i, 1);
			} else if (!monster.jumping && monster.hitbox.intersects(player.hitbox)) {
				effects.push(new BloodsplatterEffect(self, monster.x, monster.y));
				monster.destroy();
				monsters.splice(i, 1);
				if (interface.changeLives(-1)) {
					LK.showGameOver();
				}
			}
		}
	});
	function updatePosition(event) {
		var pos = event.getLocalPosition(self);
		targetPosition.x = pos.x;
		targetPosition.y = pos.y;
	}
});
:quality(85)/https://cdn.frvr.ai/657b7a5de5f77c7f98342238.png%3F3) 
 two vertical lines with a blank background.
:quality(85)/https://cdn.frvr.ai/65871806d75e33d7b5d5f68f.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/6587181bd75e33d7b5d5f692.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/65871928d75e33d7b5d5f6a9.png%3F3) 
 8 bit. cartoon. shark. ingame asset. seen from the front. open mouth. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
:quality(85)/https://cdn.frvr.ai/658719eed75e33d7b5d5f6bc.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/65871b75d75e33d7b5d5f6db.png%3F3) 
 8-bit. cartoon. gold coin. in game asset. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
:quality(85)/https://cdn.frvr.ai/65871c55d75e33d7b5d5f6fa.png%3F3) 
 8-bit. cartoon. old boot from ocean. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
:quality(85)/https://cdn.frvr.ai/65871db3d75e33d7b5d5f705.png%3F3) 
 8 bit blood spatter. cartoon. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
:quality(85)/https://cdn.frvr.ai/65871f58d75e33d7b5d5f72a.png%3F3) 
 8 bit. cartoon. old car tire. floating in the water. in game asset. no background. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
:quality(85)/https://cdn.frvr.ai/6587200ad75e33d7b5d5f73c.png%3F3) 
 8 bit. cartoon. tree log. floating in the water. in game asset. no background. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
:quality(85)/https://cdn.frvr.ai/658720bad75e33d7b5d5f748.png%3F3) 
 8 bit. cartoon. big buoy. floating in the water. in game asset. no background. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
:quality(85)/https://cdn.frvr.ai/65872236d75e33d7b5d5f77d.png%3F3) 
 8 bit. cartoon. bottle with a message . floating in the water. in game asset. no background. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
:quality(85)/https://cdn.frvr.ai/658722a0d75e33d7b5d5f786.png%3F3) 
 8 bit. cartoon. old open can. rusty . floating in the water. in game asset. no background. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.