/**** 
* Classes
****/
var ConfigContainer = Container.expand(function (config) {
	var self = Container.call(this);
	config = config || {};
	;
	self.x = config.x || 0;
	self.y = config.y || 0;
	self.rotation = config.rotation || 0;
	;
	return self;
});
/** 
* var config = {
* 	x        : Number || 0, // See: ConfigContainer
* 	y        : Number || 0, // See: ConfigContainer
* 	rotation : Number || 0, // See: ConfigContainer
* 	anchorX  : Number || 0,
* 	anchorY  : Number || 1,
* 	size     : Number || TEXT_DEFAULT_SIZE,
* 	font     : String || TEXT_DEFAULT_FONT,
* 	fill     : String || TEXT_DEFAULT_FILL,
* 	border   : String || TEXT_DEFAULT_BORDER,
* }
**/
var BorderedText = ConfigContainer.expand(function (text, config) {
	var self = ConfigContainer.call(this, config);
	config = config || {};
	var anchorX = config.anchorX !== undefined ? config.anchorX : 0;
	var anchorY = config.anchorY !== undefined ? config.anchorY : 1;
	var size = config.size !== undefined ? config.size : TEXT_DEFAULT_SIZE;
	var font = config.font !== undefined ? config.font : TEXT_DEFAULT_FONT;
	var textFill = config.fill !== undefined ? config.fill : TEXT_DEFAULT_FILL;
	var borderFill = config.border !== undefined ? config.border : TEXT_DEFAULT_BORDER;
	var mainAsset;
	var textAssets = [];
	;
	self.setText = setText;
	self.setFill = setFill;
	;
	function setText(newText) {
		for (var i = 0; i < textAssets.length; i++) {
			textAssets[i].setText(newText);
		}
	}
	function setFill(newFill) {
		textFill = newFill;
		mainAsset.fill = newFill;
	}
	function buildTextAssets(newText) {
		for (var i = 0; i < TEXT_OFFSETS.length; i++) {
			var main = i === TEXT_OFFSETS.length - 1;
			var textAsset = textAssets[i];
			if (textAsset) {
				textAsset.destroy();
			}
			textAsset = self.addChild(new Text2(newText, {
				fill: main ? textFill : borderFill,
				font: font,
				size: size
			}));
			textAsset.anchor = {
				x: anchorX,
				y: anchorY
			}; // NOTE: Cannot be set in config
			textAsset.x = TEXT_OFFSETS[i][0] * TEXT_BORDER_WEIGHT; // NOTE: Cannot be set in config
			textAsset.y = TEXT_OFFSETS[i][1] * TEXT_BORDER_WEIGHT; // NOTE: Cannot be set in config
			textAssets[i] = textAsset;
		}
		mainAsset = textAssets[TEXT_OFFSETS.length - 1];
	}
	;
	buildTextAssets(text);
	return self;
});
/** 
* var config = {
* 	x        : Number || 0,         // See: ConfigContainer
* 	y        : Number || 0,         // See: ConfigContainer
* 	rotation : Number || 0,         // See: ConfigContainer
* 	anchorX  : Number || .5,
* 	anchorY  : Number || .5,
* 	scale    : Number || undefined,
* 	scaleX   : Number || scale,
* 	scaleY   : Number || scale,
* 	width    : Number || undefined, // Auto-calculated if left undefined and height is set
* 	height   : Number || undefined, // Auto-calculated if left undefined and width is set
* 	tint     : String || 0xFFFFFF,  // Not tinted by default
* 	border   : String || TEXT_DEFAULT_BORDER,
* }
**/
var BorderedSymbol = ConfigContainer.expand(function (symbol, config) {
	var self = ConfigContainer.call(this, config);
	config = config || {};
	var width = config.width !== undefined ? config.width : undefined;
	var height = config.height !== undefined ? config.height : undefined;
	var scale = config.scale !== undefined ? config.scale : undefined;
	var scaleX = config.scaleX !== undefined ? config.scaleX : scale;
	var scaleY = config.scaleY !== undefined ? config.scaleX : scale;
	var anchorX = config.anchorX !== undefined ? config.anchorX : .5;
	var anchorY = config.anchorY !== undefined ? config.anchorY : .5;
	var symbolTint = config.tint !== undefined ? config.tint : 0xFFFFFF;
	var borderTint = config.border !== undefined ? config.border : TEXT_DEFAULT_BORDER;
	var mainSymbol;
	var symbolAssets = [];
	;
	self.setSymbol = buildSymbolAssets;
	self.setTint = setTint;
	;
	function setTint(newTint) {
		// NOTE: Tinting is currently broken (cannot use string)
		// mainSymbol.tint = newTint;
		// symbolConfig.tint = newTint;
	}
	function buildSymbolAssets(newSymbol) {
		for (var i = 0; i < TEXT_OFFSETS.length; i++) {
			var main = i === TEXT_OFFSETS.length - 1;
			var symbolAsset = symbolAssets[i];
			if (symbolAsset) {
				symbolAsset.destroy();
			}
			symbolAsset = self.attachAsset(newSymbol, {
				// tint: main ? symbolTint : borderTint,
				tint: main ? 0xFFFFFF : 0x000000,
				// NOTE: Tinting is currently broken (cannot use string)
				x: TEXT_OFFSETS[i][0] * TEXT_BORDER_WEIGHT,
				y: TEXT_OFFSETS[i][1] * TEXT_BORDER_WEIGHT,
				anchorX: anchorX,
				anchorY: anchorY
			});
			if (width !== undefined && height === undefined) {
				height = symbolAsset.height * (width / symbolAsset.width);
			} else if (height !== undefined && width === undefined) {
				width = symbolAsset.width * (height / symbolAsset.height);
			}
			symbolAsset.width = width;
			symbolAsset.height = height;
			if (scaleX !== undefined && scaleY !== undefined) {
				symbolAsset.scale.set(scaleX, scaleY);
			}
			symbolAssets[i] = symbolAsset;
		}
		mainSymbol = symbolAssets[TEXT_OFFSETS.length - 1];
	}
	;
	buildSymbolAssets(symbol);
	return self;
});
/** 
* var config = {
* 	x        : Number  || 0,         // See: ConfigContainer
* 	y        : Number  || 0,         // See: ConfigContainer
* 	rotation : Number  || 0,         // See: ConfigContainer
* 	prefix   : Boolean || true,
* 	suffix   : Boolean || false,     // Overriden by prefix
* 	tint     : Boolean || false,     // Whether to tint the symbol
* 	anchorX  : Number  || 0,
* 	anchorY  : Number  || 1,
* 	margin   : Number  || TEXT_DEFAULT_MARGIN,
* 	size     : Number  || TEXT_DEFAULT_SIZE,
* 	scale    : Number  || undefined, // See: BorderedSymbol
* 	fill     : String  || undefined, // See: BorderedText, BorderedSymbol (tint)
* 	border   : Number  || undefined, // See: BorderedText, BorderedSymbol
* }
**/
var SymbolText = ConfigContainer.expand(function (text, symbol, config) {
	var self = ConfigContainer.call(this, config);
	config = config || {};
	var prefix = !!config.prefix || !config.suffix;
	var tint = !!config.tint;
	var anchorX = config.anchorX !== undefined ? config.anchorX : 0;
	var anchorY = config.anchorY !== undefined ? config.anchorY : 1;
	var margin = config.margin !== undefined ? config.margin : TEXT_DEFAULT_MARGIN;
	var size = config.size !== undefined ? config.size : TEXT_DEFAULT_SIZE;
	var symbolHeight = config.scale === undefined ? size : undefined;
	var textAsset = self.addChild(new BorderedText(text, {
		anchorX: 0,
		anchorY: 0.5,
		font: config.font,
		// Passthrough
		fill: config.fill,
		// Passthrough
		border: config.border,
		// Passthrough
		size: size
	}));
	var symbolAsset = self.addChild(new BorderedSymbol(symbol, {
		anchorX: 0,
		anchorY: 0.5,
		height: symbolHeight,
		tint: tint ? config.fill : undefined,
		border: config.border,
		// Passthrough
		scale: config.scale
	}));
	;
	self.setText = setText;
	self.setFill = setFill;
	self.setSymbol = setSymbol;
	;
	function alignAssets() {
		var first = prefix ? symbolAsset : textAsset;
		var second = prefix ? textAsset : symbolAsset;
		var totalWidth = symbolAsset.width + margin + textAsset.width;
		var heightOffset = Math.max(first.height, second.height) * (0.5 - anchorY);
		first.x = -totalWidth * anchorX;
		first.y = heightOffset;
		second.x = first.x + first.width + margin;
		second.y = heightOffset;
	}
	function setText(newText) {
		textAsset.setText(newText);
		alignAssets();
	}
	function setFill(newFill) {
		textAsset.setFill(newFill);
		if (tint) {
			symbolAsset.setTint(newFill);
		}
	}
	function setSymbol(newSymbol) {
		symbolAsset.setSymbol(newSymbol);
		alignAssets();
	}
	;
	alignAssets();
});
var Player = ConfigContainer.expand(function (config) {
	var self = ConfigContainer.call(this, config);
	var actionCountdown = PLAYER_ACTION_INTERVAL;
	var playerGraphics = self.attachAsset('player', {
		anchorX: 0.5,
		anchorY: 0.75
	});
	;
	self.update = update;
	self.angle = 0;
	;
	function update() {
		var currentAngle = Math.atan2(self.y, self.x);
		var angleDifference = mod(targetAngle - currentAngle + Math.PI, MATH_2_PI) - Math.PI;
		if (approxZero(angleDifference)) {
			if (taskLaunch) {
				self.destroy();
				player = undefined;
				LK.setTimeout(function () {
					ship.launch(1);
				}, PLAYER_SPAWN_DELAY);
			} else if (--actionCountdown <= 0) {
				performAction(currentAngle);
				actionCountdown = PLAYER_ACTION_INTERVAL;
			}
		} else {
			var direction = angleDifference / Math.abs(angleDifference);
			var angleStep = PLAYER_SPEED / planet.radius;
			var angleIncrease = Math.min(Math.abs(angleDifference), angleStep);
			var newAngle = currentAngle + direction * angleIncrease;
			self.x = Math.cos(newAngle) * planet.radius;
			self.y = Math.sin(newAngle) * planet.radius;
			self.angle = newAngle;
			stats.distanceRun += angleIncrease * planet.radius;
			playerGraphics.scale.x = direction < 0 ? -1 : 1;
			self.rotation = newAngle + MATH_HALF_PI;
			actionCountdown = 0; // Perform action immediately after stopping
		}
	}
	function performAction(angle) {
		var count = planet.nodes.length;
		var baseIndex = mod(Math.round(angle / MATH_2_PI * count), count);
		var baseNode = planet.nodes[baseIndex];
		if (baseNode && !baseNode.tryAction()) {
			var iterations = Math.floor(PLAYER_ACTION_DIST / (planet.radius * MATH_2_PI / planet.nodes.length));
			var prev = baseNode.prev;
			var next = baseNode.next;
			while (iterations > 0) {
				iterations--;
				if (next && next.tryAction()) {
					break;
				}
				if (prev && prev.tryAction()) {
					break;
				}
				prev = prev ? prev.prev : null;
				next = next ? next.next : null;
			}
		}
	}
});
var Ship = Container.expand(function (x, y) {
	var self = Container.call(this);
	var background = self.addChild(new Container());
	var shipGraphics = self.attachAsset('ship', {
		anchorX: 0.10,
		anchorY: 0.5
	});
	var counter = 0;
	var particles = [];
	background.rotation = -MATH_HALF_PI;
	;
	self.update = update;
	self.launch = launch;
	self.x = x;
	self.y = y;
	self.direction = -1;
	;
	function update() {
		if (self.direction < 0) {
			self.x -= ROCKET_SPEED_REVERSE;
			if (self.x <= planet.radius) {
				self.x = planet.radius;
				self.direction = 0;
				LK.setTimeout(function () {
					player = planet.addChild(new Player({
						rotation: ship.rotation,
						x: ship.x,
						y: ship.y
					}));
				}, PLAYER_SPAWN_DELAY);
			}
		} else if (self.direction > 0) {
			counter++;
			var speed = Math.pow(ROCKET_SPEED_BASE, counter / ROCKET_SPEED_DIV - ROCKET_SPEED_DELAY);
			self.x += speed;
			if (self.x > ROCKET_DIST_LEAVE) {
				counter = 0;
				self.direction = -1;
				transitionPlanets();
			}
		}
		if (self.direction) {
			particles.push(background.addChild(new FlameParticle({
				x: (Math.random() - 0.5) * ROCKET_FLAME_BREDTH
			})));
		}
		updateList(particles);
	}
	function launch(direction) {
		if (!self.direction) {
			self.direction = direction;
		}
	}
});
var FlameParticle = ConfigContainer.expand(function (config) {
	var self = ConfigContainer.call(this, config);
	var flameGraphics = self.attachAsset('flame', {
		anchorX: 0.5,
		anchorY: 0.5,
		alpha: 0.5
	});
	var lifetime = ROCKET_FLAME_LIFETIME;
	;
	self.update = update;
	;
	function update() {
		var remaining = lifetime / ROCKET_FLAME_LIFETIME;
		self.y -= ROCKET_FLAME_SPEED;
		flameGraphics.alpha = 0.5 * remaining;
		flameGraphics.scale.set(remaining);
		return --lifetime <= 0;
	}
});
var Plant = ConfigContainer.expand(function (type, growthStage, potted, parentPlanet, config) {
	var self = ConfigContainer.call(this, config);
	var details = PLANT_DETAILS[type];
	var scale = 1 + randomFloatInRange(-PLANT_SCALE_VARIANCE, PLANT_SCALE_VARIANCE);
	;
	self.update = update;
	self.uproot = uproot;
	self.action = action;
	self.harvest = harvest;
	self.updateStage = updateStage;
	self.refreshGraphics = refreshGraphics;
	self.refreshCountdown = refreshCountdown;
	self.type = type;
	self.fruit = details.fruit;
	self.fruitCount = 0;
	self.potted = potted;
	self.graphics;
	self.growthStage = growthStage;
	self.growthStages = details.stages;
	self.growthTime = details.growthTime;
	self.growthVariance = details.growthVariance;
	self.growthCountdown = 0;
	self.skipFinalGrowth = false;
	self.fruitOffsetMin = 0;
	self.fruitOffsetMax = 0;
	self.fruitRange = 0;
	self.harvest;
	self.strong = false; // Whether the plant can prevent weeds overrunning it
	self.actionable = false; // Whether passive actions can be performed on this plant
	self.uprootable = false; // Whether the plant can be manually uprooted (with the uproot button)
	;
	function update() {
		if (--self.growthCountdown <= 0 && self.growthStage < self.growthStages) {
			self.updateStage(self.growthStage + 1);
		}
	}
	function uproot(overrun) {
		if (overrun) {
			if (!self.strong) {
				stats.cropsOverrun++;
				self.harvest();
				return true;
			}
		} else if (self.uprootable) {
			self.harvest();
			return true;
		}
		return false;
	}
	function action() {
		return self.actionable;
	}
	function harvest(quantity) {
		var max = Math.min(self.fruitCount, quantity || Infinity);
		for (var i = 0; i < max; i++) {
			self.fruitCount--;
			var fruitOffset = randomFloatInRange(self.fruitOffsetMin, self.fruitOffsetMax);
			var rotation = self.parent.rotation;
			var fruitX = self.parent.x + Math.cos(rotation + MATH_HALF_PI) * (self.y - fruitOffset);
			var fruitY = self.parent.y + Math.sin(rotation + MATH_HALF_PI) * (self.y - fruitOffset);
			if (self.fruitRange > 0) {
				var rangeRotation = Math.random() * MATH_2_PI;
				var rangeDistance = Math.random() * self.fruitRange;
				fruitX += Math.cos(rangeRotation) * rangeDistance;
				fruitY += Math.sin(rangeRotation) * rangeDistance;
			}
			new Fruit(self.fruit, parentPlanet, {
				x: fruitX,
				y: fruitY,
				rotation: rotation
			});
		}
	}
	function updateStage(newStage) {
		if (newStage !== self.growthStages || !self.skipFinalGrowth) {
			self.growthStage = newStage;
			self.refreshGraphics();
		}
		if (self.growthStage !== self.growthStages) {
			self.refreshCountdown();
		}
	}
	function refreshGraphics() {
		if (self.graphics) {
			self.graphics.destroy();
		}
		self.graphics = self.createAsset(self.type + self.growthStage, {
			anchorX: 0.5,
			anchorY: 1,
			scaleX: scale * Math.random() < 0.5 ? 1 : -1,
			scaleY: scale
		});
	}
	function refreshCountdown() {
		var baseTime = self.growthTime + Math.random() * self.growthVariance;
		self.growthCountdown = Math.floor(PLANT_GROWTH_FACTOR * baseTime);
	}
	;
	return self;
});
var PlantWeeds = Plant.expand(function (growthStage, potted, parentPlanet, config) {
	var self = Plant.call(this, 'plantWeeds', growthStage, potted, parentPlanet, config);
	var baseUpdate = self.update;
	var baseHarvest = self.harvest;
	var baseUpdateStage = self.updateStage;
	var spreadCountdown = 0;
	;
	self.update = update;
	self.action = action;
	self.harvest = harvest;
	self.updateStage = updateStage;
	self.strong = true;
	self.actionable = !potted;
	self.uprootable = potted;
	;
	function update() {
		baseUpdate();
		;
		if (self.growthStage === self.growthStages && --spreadCountdown <= 0) {
			var node = self.parent;
			var nextPlant = node.next.plant;
			var prevPlant = node.prev.plant;
			var nextSpreadable = !nextPlant || !nextPlant.strong;
			var prevSpreadable = !prevPlant || !prevPlant.strong;
			if (nextSpreadable || prevSpreadable) {
				var targetNode;
				if (nextSpreadable && prevSpreadable) {
					targetNode = Math.random() < 0.5 ? node.next : node.prev;
				} else {
					targetNode = nextSpreadable ? node.next : node.prev;
				}
				targetNode.addPlant(self.type, 1, true);
			}
			refreshCountdown();
		}
	}
	function action() {
		if (self.actionable) {
			self.harvest();
			self.parent.removePlant();
			return true;
		}
		return false;
	}
	function harvest(quantity) {
		baseHarvest(quantity);
		;
		stats.weedsPulled++;
	}
	function updateStage(newStage) {
		baseUpdateStage(newStage);
		;
		if (newStage === self.growthStages) {
			self.fruitCount = 2;
			self.fruitOffsetMin = 85;
			self.fruitOffsetMax = 95;
		} else if (newStage === self.growthStages - 1) {
			self.fruitCount = 1;
			self.fruitOffsetMin = 35;
			self.fruitOffsetMax = 45;
		}
	}
	function refreshCountdown() {
		var baseTime = WEEDS_SPREAD_TIME + Math.random() * WEEDS_SPREAD_VARIANCE;
		spreadCountdown = Math.floor(PLANT_GROWTH_FACTOR * baseTime);
	}
	;
	self.updateStage(growthStage);
});
var PlantBush = Plant.expand(function (growthStage, potted, parentPlanet, config) {
	var self = Plant.call(this, 'plantBush', growthStage, potted, parentPlanet, config);
	var baseUpdateStage = self.updateStage;
	var baseHarvest = self.harvest;
	var maxFruitCount = 3;
	var fruitRemaining = 6;
	var fruitScale = 0.8;
	var fruitYFactor = 0.5;
	var fruitAssets = [];
	;
	self.updateStage = updateStage;
	self.harvest = harvest;
	self.action = action;
	self.skipFinalGrowth = true;
	self.fruitOffsetMin = 45;
	self.fruitOffsetMax = 45;
	self.fruitRange = 30;
	;
	function updateStage(newStage) {
		baseUpdateStage(newStage);
		;
		if (newStage === self.growthStages) {
			self.strong = true;
			if (!self.skipFinalGrowth) {
				self.graphics.tint = PLANT_DEAD_COLOUR;
				self.uprootable = true;
				self.actionable = false;
				self.harvest();
			}
			if (fruitRemaining-- <= 0) {
				self.skipFinalGrowth = false;
			} else if (self.fruitCount < maxFruitCount) {
				self.fruitCount++;
				self.actionable = true;
				updateVisibleFruit();
			}
		}
	}
	function harvest(quantity) {
		baseHarvest(quantity);
		;
		self.actionable = self.fruitCount > 0;
		updateVisibleFruit();
	}
	function action() {
		if (self.actionable && self.fruitCount > 0) {
			self.harvest(1);
			return true;
		}
		return false;
	}
	function updateVisibleFruit() {
		var assetSlots = [];
		var emptySlots = [];
		for (var i = 0; i < maxFruitCount; i++) {
			(fruitAssets[i] ? assetSlots : emptySlots).push(i);
		}
		var change = self.fruitCount - assetSlots.length;
		while (change !== 0) {
			if (change < 0) {
				// Remove asset
				var index = randomIntInRange(0, assetSlots.length);
				var slot = assetSlots[index];
				fruitAssets[slot].destroy();
				fruitAssets[slot] = undefined;
				assetSlots.splice(index, 1);
				change++;
			} else if (change > 0) {
				// Make new asset
				var index = randomIntInRange(0, emptySlots.length);
				var slot = emptySlots[index];
				var offset = slot - (maxFruitCount / 2 - 0.5);
				var fruitX = offset * self.fruitRange;
				var fruitY = -randomFloatInRange(self.fruitOffsetMin, self.fruitOffsetMax) - Math.cos(offset * MATH_HALF_PI) * self.fruitRange * fruitYFactor;
				fruitAssets[slot] = self.attachAsset(self.fruit, {
					x: fruitX,
					y: fruitY,
					anchorX: 0.5,
					anchorY: 0.5,
					scaleX: fruitScale,
					scaleY: fruitScale,
					rotation: -MATH_QUARTER_PI
				});
				emptySlots.splice(index, 1);
				change--;
			}
		}
	}
	;
	self.updateStage(growthStage);
	return self;
});
var PlantStalk = Plant.expand(function (growthStage, potted, parentPlanet, config) {
	var self = Plant.call(this, 'plantStalk', growthStage, potted, parentPlanet, config);
	var baseUpdateStage = self.updateStage;
	var yieldStage = 5;
	var yieldPerStage = 3;
	;
	self.updateStage = updateStage;
	self.skipFinalGrowth = true;
	self.fruitOffsetMin = 200;
	self.fruitOffsetMax = 300;
	self.fruitRange = 30;
	;
	function updateStage(newStage) {
		baseUpdateStage(newStage);
		;
		if (newStage >= yieldStage) {
			self.fruitCount += yieldPerStage;
		}
		if (newStage === self.growthStages) {
			self.harvest();
			self.parent.removePlant();
		}
	}
	;
	self.updateStage(growthStage);
	return self;
});
var PlantEyeball = Plant.expand(function (growthStage, potted, parentPlanet, config) {
	var self = Plant.call(this, 'plantEyeball', growthStage, potted, parentPlanet, config);
	var baseUpdateStage = self.updateStage;
	var decorAmount = 3;
	var decorScale = 0.5;
	var fruitChance = 0.33;
	var fruitChances = 20;
	;
	self.updateStage = updateStage;
	self.skipFinalGrowth = true;
	self.fruitOffsetMin = 80;
	self.fruitOffsetMax = 140;
	self.fruitRange = 20;
	;
	function updateStage(newStage) {
		baseUpdateStage(newStage);
		;
		if (newStage === self.growthStages) {
			if (fruitChances-- > 0) {
				if (Math.random() < fruitChance) {
					self.harvest(self.fruitCount = 1);
				}
			} else {
				self.harvest(self.fruitCount = decorAmount);
				self.parent.removePlant();
			}
		} else if (newStage === self.growthStages - 1) {
			createVisuals();
		}
	}
	function createVisuals() {
		for (var i = 0; i < decorAmount; i++) {
			var decorOffset = randomFloatInRange(self.fruitOffsetMin, self.fruitOffsetMax);
			var rangeRotation = Math.random() * MATH_2_PI;
			var rangeDistance = Math.random() * self.fruitRange;
			var decorX = self.x + Math.cos(rangeRotation) * rangeDistance;
			var decorY = self.y - decorOffset + Math.sin(rangeRotation) * rangeDistance;
			self.attachAsset(self.fruit, {
				x: decorX,
				y: decorY,
				scaleX: decorScale,
				scaleY: decorScale
			});
		}
	}
	;
	self.updateStage(growthStage);
	return self;
});
var PlantFlower = Plant.expand(function (growthStage, potted, parentPlanet, config) {
	var self = Plant.call(this, 'plantFlower', growthStage, potted, parentPlanet, config);
	var baseUpdateStage = self.updateStage;
	var yieldStage = 4;
	var fruitYFactor = 0.75;
	var fruitCount = 4;
	;
	self.action = action;
	self.updateStage = updateStage;
	self.fruitRange = 50;
	;
	function action() {
		if (self.actionable) {
			self.harvest();
			self.parent.removePlant();
			return true;
		}
		return false;
	}
	function updateStage(newStage) {
		baseUpdateStage(newStage);
		;
		if (newStage === self.growthStages) {
			// Dead plant/flower
			self.fruitCount = Math.round(Math.random());
			self.graphics.tint = PLANT_DEAD_COLOUR;
			self.actionable = false;
			self.uprootable = true;
		} else if (newStage >= yieldStage) {
			// Harvestable plant/flower
			self.fruitCount = fruitCount + Math.round(Math.random());
			self.actionable = true;
			self.fruitOffsetMin = self.graphics.height * fruitYFactor;
			self.fruitOffsetMax = self.graphics.height * fruitYFactor;
		}
	}
	;
	self.updateStage(growthStage);
	return self;
});
var PlantDiamond = Plant.expand(function (growthStage, potted, parentPlanet, config) {
	var self = Plant.call(this, 'plantDiamond', growthStage, potted, parentPlanet, config);
	var baseUpdateStage = self.updateStage;
	;
	self.action = action;
	self.updateStage = updateStage;
	self.strong = true;
	self.fruitOffsetMin = 123;
	self.fruitOffsetMax = 123;
	;
	function action() {
		if (self.actionable) {
			self.harvest();
			self.parent.removePlant();
			return true;
		}
		return false;
	}
	function updateStage(newStage) {
		baseUpdateStage(newStage);
		;
		if (newStage === self.growthStages) {
			self.fruitCount = 1;
			self.actionable = true;
		}
	}
	;
	self.updateStage(growthStage);
	return self;
});
var Fruit = ConfigContainer.expand(function (fruitName, parentPlanet, config) {
	var self = ConfigContainer.call(this, config);
	var falling = true;
	var fruitGraphics = self.attachAsset(fruitName, {
		anchorX: 0.5,
		anchorY: 0.5,
		scaleX: SCALE_FRUIT_DROP * (Math.random() < 0.5 ? 1 : -1),
		scaleY: SCALE_FRUIT_DROP
	});
	;
	self.fruitName = fruitName;
	self.update = update;
	;
	function update(idle) {
		// Check if fruit is within collection range
		if (player && !idle) {
			var dx = self.x - player.x;
			var dy = self.y - player.y;
			var sqrDistance = dx * dx + dy * dy;
			if (sqrDistance < PLAYER_ACTION_SQRDIST) {
				inventory.adjustItem(fruitName, 1);
				stats.cropsHarvested++;
				var popupEffect = popupMap[fruitName];
				if (!popupEffect) {
					var rotation = player.rotation;
					var offset = parentPlanet.radius + POPUP_OFFSET;
					popupEffect = new PopupEffect(fruitName, {
						x: Math.cos(rotation - MATH_HALF_PI) * offset,
						y: Math.sin(rotation - MATH_HALF_PI) * offset,
						rotation: rotation
					});
				} else {
					popupEffect.increment();
				}
				return true;
			}
		}
		// Handle fruit falling towards the planet
		if (falling) {
			var angle = self.rotation + MATH_HALF_PI;
			self.x += Math.cos(angle) * FRUIT_FALL_SPEED;
			self.y += Math.sin(angle) * FRUIT_FALL_SPEED;
			if (self.x * self.x + self.y * self.y <= parentPlanet.radius * parentPlanet.radius) {
				falling = false;
			}
		}
	}
	;
	parentPlanet.fruitList.push(parentPlanet.addChild(self));
	return self;
});
var PopupEffect = ConfigContainer.expand(function (fruitName, config) {
	var self = ConfigContainer.call(this, config);
	var count = 1;
	var lifetime = POPUP_DURATION;
	var symbolText = self.addChild(new SymbolText(count + '×', fruitName, {
		anchorX: 0.5,
		anchorY: 1,
		suffix: true,
		scale: SCALE_FRUIT_TRADE
	}));
	;
	self.update = update;
	self.increment = increment;
	;
	function update() {
		if (--lifetime > 0) {
			symbolText.y -= 1;
			symbolText.alpha = lifetime / POPUP_DURATION;
			if (lifetime === POPUP_RECYCLE) {
				popupMap[fruitName] = undefined;
			}
		} else {
			self.destroy();
			return true;
		}
	}
	;
	function increment() {
		count++;
		symbolText.setText(count + '×');
	}
	;
	effectsList.push(planet.addChild(self));
	popupMap[fruitName] = self;
	return self;
});
var PlantNode = ConfigContainer.expand(function (config) {
	var self = ConfigContainer.call(this, config);
	;
	self.update = update;
	self.addPlant = addPlant;
	self.removePlant = removePlant;
	self.tryAction = tryAction;
	self.plant;
	self.farm;
	self.prev;
	self.next;
	;
	function update() {
		if (self.plant) {
			self.plant.update();
		}
	}
	function addPlant(plantName, stage, overrun, potted) {
		if (!self.plant || self.plant.uproot(overrun)) {
			if (self.plant) {
				self.removePlant();
			}
			var plantBlueprint = PLANT_DETAILS[plantName].blueprint;
			self.plant = self.addChild(new plantBlueprint(stage, potted, self.parent, {
				y: potted ? PLOT_FARM_OFFSET : PLOT_NODE_OFFSET
			}));
		}
	}
	function removePlant() {
		self.plant.destroy();
		self.plant = undefined;
	}
	function tryAction() {
		return !!self.plant && self.plant.actionable && self.plant.action();
	}
	;
	return self;
});
var WeedsNode = PlantNode.expand(function (config) {
	var self = PlantNode.call(this, config);
	var baseUpdate = self.update;
	var baseAddPlant = self.addPlant;
	var baseTryAction = self.tryAction;
	;
	self.update = update;
	self.addPlant = addPlant;
	self.tryAction = tryAction;
	;
	function update() {
		baseUpdate();
		if (!self.plant && --spawnCountdown <= 0) {
			self.addPlant('plantWeeds', 1, false, false);
		}
	}
	function addPlant(plantName, stage, overrun, potted) {
		baseAddPlant(plantName, stage, overrun, potted);
		refreshCountdown();
	}
	function tryAction() {
		if (baseTryAction()) {
			refreshCountdown();
			return true;
		}
		return false;
	}
	function refreshCountdown() {
		var baseTime = WEEDS_SPAWN_TIME + Math.random() * WEEDS_SPAWN_VARIANCE;
		spawnCountdown = Math.floor(PLANT_GROWTH_FACTOR * baseTime);
	}
	;
	refreshCountdown();
});
var FarmNode = PlantNode.expand(function (config) {
	var self = PlantNode.call(this, config);
	var baseUpdate = self.update;
	var baseAddPlant = self.addPlant;
	var baseTryAction = self.tryAction;
	var targetAlpha = 0;
	var planterButton;
	var uprootButton;
	var currentAlpha = targetAlpha;
	var farmGraphics = self.attachAsset('farm', {
		anchorX: 0.5,
		anchorY: 0.6,
		alpha: currentAlpha
	});
	;
	self.update = update;
	self.addPlant = addPlant;
	self.tryAction = tryAction;
	self.tryPlantFruit = tryPlantFruit;
	;
	function update() {
		baseUpdate();
		// Control the in/out fade when overrun
		if (currentAlpha !== targetAlpha) {
			if (currentAlpha < targetAlpha) {
				currentAlpha = Math.min(targetAlpha, currentAlpha + PLOT_ALPHA_STEP);
			} else {
				currentAlpha = Math.max(targetAlpha, currentAlpha - PLOT_ALPHA_STEP);
			}
			farmGraphics.alpha = currentAlpha;
		}
		// Show/hide the planter button
		var fruitName = inventory.getSelection();
		if (planterRequirements(fruitName)) {
			if (!planterButton) {
				planterButton = self.addChild(new PlanterButton(fruitName, planterButtonCallback, {
					scale: SCALE_FRUIT_PLANTER
				}));
			} else {
				planterButton.updateImage(fruitName);
			}
		} else if (planterButton) {
			planterButton.destroy();
			planterButton = undefined;
		}
		// Show/hide the uproot button
		if (uprootRequirements()) {
			if (!uprootButton) {
				uprootButton = self.addChild(new UprootButton(fruitName, uprootButtonCallback, {}));
			}
		} else if (uprootButton) {
			uprootButton.destroy();
			uprootButton = undefined;
		}
	}
	function tryAction() {
		if (baseTryAction()) {
			targetAlpha = 1;
			return true;
		} else if (taskPlanter) {
			tryPlantFruit(taskPlanter);
			taskPlanter = false;
		} else if (taskUproot) {
			tryUproot();
			taskPlanter = true;
		}
	}
	function tryUproot() {
		if (uprootRequirements()) {
			if (self.plant.uproot(false)) {
				self.removePlant();
			}
		}
	}
	function tryPlantFruit(fruitName) {
		if (planterRequirements(fruitName)) {
			var plantName = FRUIT_DETAILS[fruitName].plant;
			self.addPlant(plantName, 1, false, true);
			inventory.adjustItem(fruitName, -1);
			stats.cropsPlanted++;
		}
	}
	function addPlant(plantName, stage, overrun, potted) {
		baseAddPlant(plantName, stage, overrun, potted);
		targetAlpha = potted ? 1 : 0;
	}
	function planterRequirements(fruitName) {
		return !self.plant && currentAlpha === 1 && inventory.allowed && inventory.getQuantity(fruitName) > 0;
	}
	function planterButtonCallback() {
		resetTasks();
		taskPlanter = inventory.getSelection();
		targetAngle = Math.atan2(self.y, self.x);
		skipRetarget = true;
	}
	function uprootRequirements() {
		return !!self.plant && self.plant.uprootable && self.plant.potted;
	}
	function uprootButtonCallback() {
		resetTasks();
		taskUproot = true;
		targetAngle = Math.atan2(self.y, self.x);
		skipRetarget = true;
	}
});
var Planet = ConfigContainer.expand(function (planetName, config) {
	var self = ConfigContainer.call(this, config);
	var details = PLANET_DETAILS[planetName];
	var perimeter = MATH_2_PI * details.radius;
	var numPlots = Math.floor(perimeter / (PLOT_SIZE + PLOT_GAP));
	var numNodes = numPlots * 4;
	var nodes = [];
	var fruitList = [];
	var background = self.addChild(new Container());
	var planetScale = 2 * details.radius / 1000;
	var planetGraphics = self.createAsset(planetName, {
		anchorX: 0.5,
		anchorY: 0.5,
		scaleX: planetScale,
		scaleY: planetScale
	});
	;
	self.update = update;
	self.name = planetName;
	self.barren = !!details.barren; // No plant life
	self.coarse = !!details.coarse; // No weed nodes (still starts + grows + spreads)
	self.nodes = nodes;
	self.background = background;
	self.fruitList = fruitList;
	self.spin = details.spin;
	self.radius = details.radius;
	;
	function update(ticks) {
		self.rotation += self.spin;
		updateList(nodes);
		updateList(fruitList, planet !== self);
	}
	function createNodes() {
		if (!self.barren) {
			var defaultPlant = 'plantWeeds';
			var maxPlantStage = PLANT_DETAILS[defaultPlant].stages;
			for (var i = 0; i < numNodes; i++) {
				var angle = i / numNodes * MATH_2_PI;
				var nodeX = self.radius * Math.cos(angle);
				var nodeY = self.radius * Math.sin(angle);
				var rotation = angle + MATH_HALF_PI;
				var farmNode = i !== 0 && i % 4 === 0;
				var nodeType = farmNode ? FarmNode : i % 2 === 0 && !self.coarse ? WeedsNode : PlantNode;
				var node = self.addChild(new nodeType({
					x: nodeX,
					y: nodeY,
					rotation: rotation
				}));
				if (farmNode) {
					node.addPlant(defaultPlant, maxPlantStage);
				} else if (Math.random() < WEEDS_SPAWN_CHANCE) {
					node.addPlant(defaultPlant, 1 + Math.floor(Math.random() * maxPlantStage));
				}
				if (i !== 0) {
					node.prev = nodes[i - 1];
					node.prev.next = node;
					if (i === numNodes - 1) {
						node.next = nodes[0];
						node.next.prev = node;
					}
				}
				nodes.push(node);
			}
		}
	}
	;
	createNodes();
	return self;
});
var PlanetGrey = Planet.expand(function (config) {
	var self = Planet.call(this, 'planetGrey', config);
});
var PlanetRed = Planet.expand(function (config) {
	var self = Planet.call(this, 'planetRed', config);
});
var PlanetBlue = Planet.expand(function (config) {
	var self = Planet.call(this, 'planetBlue', config);
});
var PlanetOmni = Planet.expand(function (config) {
	var self = Planet.call(this, 'planetOmni', config);
});
var PlanetGold = Planet.expand(function (config) {
	var self = Planet.call(this, 'planetGold', config);
});
var Crosshair = Container.expand(function () {
	var self = Container.call(this);
	var counter = 0;
	var arrows = [];
	var arrowOffsets = [{
		x: 0,
		y: -1
	}, {
		x: 1,
		y: 0
	}, {
		x: 0,
		y: 1
	}, {
		x: -1,
		y: 0
	}];
	for (var i = 0; i < arrowOffsets.length; i++) {
		var arrow = self.attachAsset('chevron', {
			anchorX: 0.5,
			anchorY: 1
		});
		arrow.rotation = MATH_HALF_PI * i;
		arrows.push(arrow);
		self.addChild(arrow);
	}
	;
	self.rotation = MATH_QUARTER_PI;
	self.update = update;
	;
	function update() {
		if (currentPlanet !== destinationPlanet) {
			counter++;
			var distance = CROSSHAIR_DIST + CROSSHAIR_VARIANCE * Math.sin(counter / CROSSHAIR_PERIOD);
			setDistance(distance);
		} else if (counter > 0) {
			counter = 0;
			setDistance(CROSSHAIR_DIST);
		}
	}
	function setDistance(distance) {
		for (var i = 0; i < arrows.length; i++) {
			arrows[i].x = arrowOffsets[i].x * distance;
			arrows[i].y = arrowOffsets[i].y * distance;
		}
	}
	;
	setDistance(CROSSHAIR_DIST);
});
var NavigationButton = ConfigContainer.expand(function (planetName, callback, config) {
	var self = ConfigContainer.call(this, config);
	var details = PLANET_DETAILS[planetName];
	var buttonGraphics = self.attachAsset('navigationButton', {
		anchorX: 0.5,
		anchorY: 0.5,
		tint: 0xFFAAAA
	});
	var planetGraphics = self.createAsset(planetName, {
		anchorX: 0.5,
		anchorY: 0.5,
		width: buttonGraphics.width * 0.4,
		height: buttonGraphics.width * 0.4
	});
	var currencyText = self.addChild(new SymbolText(details.cost, 'currencySymbol', {
		anchorX: .5,
		anchorY: .5,
		tint: true
	}));
	;
	self.unlock = unlock;
	self.cost = details.cost;
	self.planet = planetName;
	self.unlocked = !details.cost;
	self.on('down', function () {
		callback(planetName);
	});
	;
	function unlock() {
		self.unlocked = true;
		buttonGraphics.tint = 0xFFFFFF;
		currencyText.destroy();
	}
	;
	if (self.unlocked) {
		unlock();
	}
});
var NavigationInterface = ConfigContainer.expand(function (config) {
	var self = ConfigContainer.call(this, config);
	var buttons = {};
	var baseOffset = -NAVIGATION.length / 2 + 0.5;
	var firstPlanet = NAVIGATION[0];
	for (var i = 0; i < NAVIGATION.length; i++) {
		var planet = NAVIGATION[i];
		var buttonX = (baseOffset + i) * 200;
		buttons[planet] = self.addChild(new NavigationButton(planet, buttonCallback, {
			x: buttonX,
			y: 0
		}));
	}
	;
	self.setDestination = setDestination;
	;
	function buttonCallback(planet) {
		var button = buttons[planet];
		if (button.unlocked) {
			setDestination(planet);
		} else if (money >= button.cost) {
			moneyDisplay.setText(money -= button.cost);
			button.unlock();
		}
	}
	function setDestination(destPlanet) {
		if (player) {
			destinationPlanet = destPlanet;
			buttons[destPlanet].addChild(crosshair);
			resetTasks();
			if (destinationPlanet !== currentPlanet) {
				taskLaunch = true;
				targetAngle = 0;
			}
		}
	}
	;
	buttonCallback(firstPlanet);
	buttons[firstPlanet].addChild(crosshair);
});
var WinningMessage = Container.expand(function (x, y, completionTicks) {
	var self = Container.call(this);
	var seconds = Math.floor(completionTicks / 60);
	var minutes = Math.floor(seconds / 60);
	var hours = Math.floor(minutes / 60);
	seconds = seconds % 60;
	minutes = minutes % 60;
	var winningTime = (hours > 0 ? hours + 'h ' : '') + (minutes > 0 ? minutes + 'm ' : '') + seconds + 's';
	var messageStatTitles = ['Distance Walked', 'Credits Earned', 'Items Collected', 'Crops Planted', 'Crops Overrun', 'Weeds Pulled', 'Trades Accepted', 'Trades Declined', 'Rocket Launches', 'Asteroid Impacts'];
	var messageStatKeys = ['distanceRun', 'creditsEarned', 'cropsHarvested', 'cropsPlanted', 'cropsOverrun', 'weedsPulled', 'salesDone', 'salesRejected', 'rocketLaunches', 'asteroidImpacts'];
	var timeText = self.addChild(new BorderedText('You reached the Gold Planet in ' + winningTime, {
		anchorX: .5,
		anchorY: 1,
		y: -TEXT_WINNING_OFFSET
	}));
	self.addChild(new BorderedText('Congratulations, you won!', {
		anchorX: .5,
		anchorY: 1,
		size: TEXT_SIZE_LARGE,
		y: timeText.y - timeText.height - 10
	}));
	self.addChild(new BorderedText(messageStatTitles.map(mapTitle).join('\n'), {
		anchorX: 1,
		anchorY: 0,
		size: TEXT_SIZE_SMALL,
		y: TEXT_WINNING_OFFSET
	}));
	self.addChild(new BorderedText(messageStatKeys.map(mapKey).join('\n'), {
		anchorX: 0,
		anchorY: 0,
		size: TEXT_SIZE_SMALL,
		y: TEXT_WINNING_OFFSET
	}));
	;
	self.x = x;
	self.y = y;
	;
	function mapTitle(title) {
		return title + ' ';
	}
	function mapKey(key) {
		var stat = winningStats[key];
		var extra = stats[key] - stat;
		switch (key) {
			case 'distanceRun':
				return ': ' + Math.round(stat * PLAYER_DISTANCE_SCALE) + 'm' + (extra ? ' [+' + Math.round(extra * PLAYER_DISTANCE_SCALE) + 'm]' : '');
			default:
				return ': ' + stat + (extra ? ' [+' + extra + ']' : '');
		}
	}
});
var InventorySlot = ConfigContainer.expand(function (index, config) {
	var self = ConfigContainer.call(this, config);
	var itemDisplay;
	var frame = self.attachAsset('inventoryFrame', {
		anchorX: .5,
		anchorY: .5
	});
	var quantityText = self.addChild(new BorderedText('', {
		size: 30,
		anchorX: .5,
		anchorY: .5,
		y: INVENTORY_SLOT_SIZE / 2.2
	}));
	frame.width = INVENTORY_SLOT_SIZE;
	frame.height = INVENTORY_SLOT_SIZE;
	;
	self.setItem = setItem;
	self.adjustQuantity = adjustQuantity;
	self.index = index;
	self.item = null;
	self.quantity = 0;
	;
	function adjustQuantity(amount) {
		self.quantity += amount;
		quantityText.setText(self.quantity);
	}
	function setItem(itemName) {
		self.item = itemName;
		if (itemDisplay) {
			itemDisplay.destroy();
		}
		itemDisplay = self.attachAsset(itemName, {
			anchorX: 0.5,
			anchorY: 0.5,
			scaleX: SCALE_FRUIT_INVENTORY,
			scaleY: SCALE_FRUIT_INVENTORY
		});
	}
	;
	self.on('down', function () {
		self.parent.selectSlot(self.index);
	});
});
var Inventory = ConfigContainer.expand(function (config) {
	var self = ConfigContainer.call(this, config);
	var slots = [];
	var itemIndices = {};
	var selectedIndex = 0;
	var widthOffset = INVENTORY_SLOT_SIZE * INVENTORY_COLS * (config.anchorX || 0);
	var heightOffset = INVENTORY_SLOT_SIZE * INVENTORY_ROWS * (config.anchorY || 0);
	for (var row = 0; row < INVENTORY_ROWS; row++) {
		for (var col = 0; col < INVENTORY_COLS; col++) {
			var index = row * INVENTORY_COLS + col;
			slots.push(self.addChild(new InventorySlot(index, {
				x: (col + .5) * INVENTORY_SLOT_SIZE - widthOffset,
				y: (row + .5) * INVENTORY_SLOT_SIZE - heightOffset
			})));
		}
	}
	var selector = new InventorySelector();
	var warningText = self.addChild(new BorderedText('', {
		y: INVENTORY_SLOT_SIZE * INVENTORY_ROWS / 2 + 20,
		anchorX: 0.5,
		anchorY: 0,
		size: TEXT_SIZE_SMALL
	}));
	warningText.visible = false;
	;
	self.adjustItem = adjustItem;
	self.selectSlot = selectSlot;
	self.getQuantity = getQuantity;
	self.getSelection = getSelection;
	self.refreshAllowed = refreshAllowed;
	self.selector = selector;
	self.allowed = false;
	;
	function selectSlot(index) {
		slots[index].addChild(selector);
		selectedIndex = index;
		refreshAllowed();
		resetTasks();
	}
	function adjustItem(item, quantity) {
		var slotIndex = itemIndices[item];
		var newItem = false;
		if (slotIndex === undefined) {
			for (slotIndex = 0; slotIndex < slots.length; slotIndex++) {
				if (!slots[slotIndex].item) {
					break;
				}
			}
			slots[slotIndex].setItem(item);
			newItem = true;
		}
		itemIndices[item] = slotIndex;
		slots[slotIndex].adjustQuantity(quantity);
		if (newItem && slotIndex === selectedIndex) {
			slots[slotIndex].addChild(selector);
			refreshAllowed();
		}
	}
	function getSelection() {
		return slots[selectedIndex].item;
	}
	function getQuantity(item) {
		var slotIndex = itemIndices[item];
		return slotIndex === undefined ? 0 : slots[slotIndex].quantity;
	}
	function refreshAllowed() {
		var selection = getSelection();
		var allowed = true;
		if (selection) {
			var itemDetails = FRUIT_DETAILS[selection];
			var allowedTypes = PLANET_DETAILS[planet.name].plantTypes;
			allowed = allowedTypes.includes(itemDetails.type);
			if (!allowed) {
				var warning = planet.barren ? GROW_WARNING_BARREN : !itemDetails.type || !itemDetails.plant ? GROW_WARNING_DIAMOND : GROW_WARNING_GENERAL;
				warningText.setText(warning);
			}
		}
		self.allowed = allowed;
		selector.toggleAllowed(allowed);
		warningText.visible = !allowed;
	}
	;
	selectSlot(0);
	adjustItem('fruitWeeds', 0);
});
var InventorySelector = Container.expand(function () {
	var self = Container.call(this);
	var selector = self.attachAsset('inventorySelector', {
		anchorX: 0.5,
		anchorY: 0.5,
		tint: 0xFF3333
	});
	var crossAsset = self.attachAsset('cross', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	;
	self.toggleAllowed = toggleAllowed;
	;
	function toggleAllowed(bool) {
		crossAsset.alpha = bool ? 0 : 1;
	}
});
var BasicButton = ConfigContainer.expand(function (imageName, callback, config) {
	var self = ConfigContainer.call(this, config);
	config = config || {};
	var anchorX = config.anchorX !== undefined ? config.anchorX : 0.5;
	var anchorY = config.anchorY !== undefined ? config.anchorY : 0.5;
	var scale = config.scale !== undefined ? config.scale : 1;
	var scaleX = config.scaleX !== undefined ? config.scaleX : scale;
	var scaleY = config.scaleY !== undefined ? config.scaleY : scale;
	var buttonBackground = self.attachAsset('buttonBackground', {
		anchorX: anchorX,
		anchorY: anchorY
	});
	var imageAsset;
	if (imageName) {
		imageAsset = self.attachAsset(imageName, {
			x: buttonBackground.x - buttonBackground.width * (anchorX - 0.5),
			y: buttonBackground.y - buttonBackground.height * (anchorY - 0.5),
			anchorX: 0.5,
			anchorY: 0.5
		});
	}
	;
	self.updateImage = updateImage;
	self.imageName = imageName;
	self.imageAsset = imageAsset;
	self.buttonBackground = buttonBackground;
	;
	function updateImage(newImageName) {
		if (newImageName !== self.imageName) {
			self.imageName = newImageName;
			if (imageAsset) {
				imageAsset.destroy();
			}
			if (newImageName) {
				imageAsset = self.attachAsset(newImageName, {
					x: buttonBackground.x - buttonBackground.width * (anchorX - 0.5),
					y: buttonBackground.y - buttonBackground.height * (anchorY - 0.5),
					anchorX: 0.5,
					anchorY: 0.5
				});
			}
		}
	}
	;
	buttonBackground.on('down', callback);
	return self;
});
var PlanterButton = BasicButton.expand(function (plantName, callback, config) {
	var self = BasicButton.call(this, plantName, callback, config);
	var arrow = self.attachAsset('arrow', {
		y: PLOT_BUTTON_OFFSET / 2,
		anchorX: 0.5,
		anchorY: 0.5,
		scaleX: 1.4,
		scaleY: 1.4
	});
	;
	self.buttonBackground.y = PLOT_BUTTON_OFFSET;
	self.imageAsset.y = PLOT_BUTTON_OFFSET;
	;
	return self;
});
var UprootButton = BasicButton.expand(function (plantName, callback, config) {
	var self = BasicButton.call(this, 'cross', callback, config);
	;
	self.buttonBackground.y = PLOT_BUTTON_OFFSET;
	self.imageAsset.y = PLOT_BUTTON_OFFSET;
	;
	return self;
});
var Background = Container.expand(function () {
	var self = Container.call(this);
	var backgroundGraphics = self.attachAsset('background', {
		x: GAME_WIDTH / 2,
		y: GAME_HEIGHT / 2,
		anchorX: 0.5,
		anchorY: 0.5,
		alpha: 0.5
	});
	backgroundGraphics.width = GAME_WIDTH * 1.05;
	backgroundGraphics.height = GAME_WIDTH * 1.05;
	;
	self.refresh = refresh;
	;
	function refresh() {
		backgroundGraphics.rotation += Math.PI / 2;
	}
});
var Asteroid = ConfigContainer.expand(function (config) {
	var self = ConfigContainer.call(this, config);
	var randomScale = ASTEROID_SCALE_MIN + Math.random() * ASTEROID_SCALE_VAR;
	var asteroidGraphics = self.attachAsset('asteroid', {
		anchorX: 0.5,
		anchorY: 0.5,
		scaleX: randomScale,
		scaleY: randomScale,
		rotation: Math.random() * MATH_2_PI
	});
	var side = self.x < 0;
	var targetY = ASTEROID_TARGET_OFFSET + Math.random() * (GAME_HEIGHT - 2 * ASTEROID_TARGET_OFFSET);
	var targetX = side ? GAME_WIDTH + ASTEROID_MARGIN : -ASTEROID_MARGIN;
	var rotationSpeed = (Math.random() - 0.5) * 2 * ASTEROID_ROTATION_MAX;
	var angle = Math.atan2(targetY - self.y, targetX - self.x);
	var velocityX = ASTEROID_SPEED * Math.cos(angle);
	var velocityY = ASTEROID_SPEED * Math.sin(angle);
	;
	self.update = update;
	;
	function update() {
		self.rotation += rotationSpeed;
		self.x += velocityX;
		self.y += velocityY;
		var dx = self.x - planet.x;
		var dy = self.y - planet.y;
		var distanceSquared = dx * dx + dy * dy;
		if (distanceSquared <= planet.radius * planet.radius) {
			if (Math.random() < ASTEROID_DROP_CHANCE) {
				var angle = Math.atan2(self.y - planet.y, self.x - planet.x) - planet.rotation;
				var fruitX = Math.cos(angle) * planet.radius;
				var fruitY = Math.sin(angle) * planet.radius;
				var fruit = planet.addChild(new Fruit('fruitDiamondDust', planet, {
					rotation: angle + MATH_HALF_PI,
					x: fruitX,
					y: fruitY
				}));
			}
			var explosion = game.addChild(new Explosion({
				x: self.x,
				y: self.y,
				scale: randomScale
			}));
			stats.asteroidImpacts++;
			return true;
		}
		return side ? self.x > targetX : self.x < targetX;
	}
});
var Explosion = ConfigContainer.expand(function (config) {
	var self = ConfigContainer.call(this, config);
	var baseScale = config.scale;
	var frameCount = 0;
	var explosionGraphics = self.attachAsset('explosion', {
		anchorX: 0.25,
		anchorY: 0.5,
		scaleX: baseScale,
		scaleY: baseScale,
		alpha: 0.5,
		rotation: Math.atan2(self.y - planet.y, self.x - planet.x)
	});
	;
	self.update = update;
	;
	function update() {
		frameCount++;
		explosionGraphics.alpha -= 0.5 / EXPLOSION_FRAMES;
		explosionGraphics.scale.set(baseScale * (1 + frameCount / EXPLOSION_FRAMES));
		if (frameCount >= EXPLOSION_FRAMES) {
			self.destroy();
			return true;
		}
	}
	;
	effectsList.push(self);
});
var Trader = ConfigContainer.expand(function (config, setTrade) {
	var self = ConfigContainer.call(this, config);
	var index = config.index;
	var angle = Math.random() * MATH_2_PI;
	var distance = Math.random() * TRADER_SPAWN_RADIUS;
	var direction = 1;
	var spawnTick = LK.ticks;
	var counter = TRADER_SPAWN_TIME;
	var traderGraphics = self.attachAsset('trader', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: Math.cos(angle) * distance,
		y: Math.sin(angle) * distance,
		scaleX: 0,
		scaleY: 0
	});
	var moveX = -traderGraphics.x / TRADER_SPAWN_TIME;
	var moveY = -traderGraphics.y / TRADER_SPAWN_TIME;
	var trade = setTrade || generateTrade();
	var traderDialogue;
	;
	self.update = update;
	self.handleTradeCallback = handleTradeCallback;
	;
	function update() {
		if ((LK.ticks - spawnTick) % TRADER_MOVE_INTERVAL === 0) {
			var anchorX = 0.5 + TRADER_MOVE_ANCHOR * (Math.random() * 2 - 1);
			var anchorY = 0.5 + TRADER_MOVE_ANCHOR * (Math.random() * 2 - 1);
			var rotation = TRADER_MOVE_ROTATION * (Math.random() * 2 - 1);
			traderGraphics.anchor.set(anchorX, anchorY);
			traderGraphics.rotation = rotation;
		}
		if (direction !== 0) {
			counter -= direction;
			traderGraphics.x += moveX * direction;
			traderGraphics.y += moveY * direction;
			traderGraphics.scale.set(1 - counter / TRADER_SPAWN_TIME);
			if (counter <= 0) {
				direction = 0;
				traderDialogue = foreground.addChild(new TraderDialogue(handleTradeCallback, trade, {
					x: self.x,
					y: self.y - traderGraphics.height / 2 - TRADER_SHIP_SPACING,
					flipX: self.x < GAME_WIDTH / 2 ? -1 : 1
				}));
			} else if (counter >= TRADER_SPAWN_TIME) {
				traders[index] = undefined;
				self.destroy();
			}
		}
	}
	function handleTradeCallback(accepted) {
		var closeDialogue = false;
		skipRetarget = true;
		if (accepted) {
			if (checkValue(trade.sellName, trade.sellAmount)) {
				makeAdjustment(trade.sellName, -trade.sellAmount, false);
				makeAdjustment(trade.buyName, trade.buyAmount, true);
				stats.salesDone++;
				closeDialogue = true;
			} else {
				traderDialogue.setTint(0xFF0000);
			}
		} else {
			stats.salesRejected++;
			closeDialogue = true;
		}
		if (closeDialogue) {
			direction = -1;
			if (traderDialogue) {
				traderDialogue.destroy();
			}
		}
	}
	function generateTrade() {
		// Determine what the trade items are available
		var details = PLANET_DETAILS[planet.name];
		var buyList = details.buy;
		var buyName = buyList[randomInt(0, buyList.length)];
		var sellList = details.sell.filter(function (item) {
			return item !== buyName;
		});
		var sellName = sellList[randomInt(0, sellList.length)];
		// Determine trade value and quantities
		var sellAmount, buyAmount;
		var valueFactor = 1 + TRADER_COST_VARIANCE * (Math.random() * 2 - 1);
		if (sellName === 'credits') {
			// Player buying Fruit with Credits
			var value = valueFactor * FRUIT_DETAILS[buyName].value;
			buyAmount = randomInt(TRADER_FRUIT_OFFER_MIN, TRADER_FRUIT_OFFER_MAX);
			sellAmount = Math.round(buyAmount * value);
		} else if (buyName === 'credits') {
			// Player selling Fruit for Credits
			var fruitDetails = FRUIT_DETAILS[sellName];
			var minSell = fruitDetails.minSell || TRADER_MINIMUM_BUY;
			var value = valueFactor * FRUIT_DETAILS[sellName].value;
			var quantity = Math.max(1, inventory.getQuantity(sellName));
			sellAmount = Math.max(minSell, randomInt(quantity * TRADER_INVENTORY_MIN, quantity * TRADER_INVENTORY_MAX));
			buyAmount = Math.round(sellAmount * value);
		} else {
			// Fruit exchange
			var multiplier = 1 + Math.random() * (TRADER_EXCHANGE_MULTIPLIER - 1);
			var sellValue = valueFactor * FRUIT_DETAILS[buyName].value;
			var buyValue = FRUIT_DETAILS[sellName].value;
			var lowValue = Math.min(sellValue, buyValue);
			sellAmount = Math.ceil(multiplier * sellValue / lowValue);
			buyAmount = Math.ceil(multiplier * buyValue / lowValue);
		}
		return {
			sellName: sellName,
			sellAmount: sellAmount,
			buyName: buyName,
			buyAmount: buyAmount
		};
	}
	function checkValue(name, amount) {
		if (name === 'credits') {
			return money >= amount;
		} else {
			return inventory.getQuantity(name) >= amount;
		}
	}
	function makeAdjustment(name, amount, collectStats) {
		if (name === 'credits') {
			moneyDisplay.setText(money += amount);
			if (collectStats) {
				stats.creditsEarned += amount;
			}
		} else {
			inventory.adjustItem(name, amount);
			if (collectStats) {
				stats.itemsCollected += amount;
			}
		}
	}
	;
	return self;
});
var TraderDialogue = ConfigContainer.expand(function (callback, trade, config) {
	var self = ConfigContainer.call(this, config);
	config = config || {};
	var flipX = config.flipX;
	var dialogueBackground = self.attachAsset('traderDialogue', {
		anchorX: TRADER_FRAME_OFFSET_X,
		anchorY: 1,
		scaleX: flipX
	});
	var centerFrameX = dialogueBackground.width * (0.5 - TRADER_FRAME_OFFSET_X) * flipX;
	var centerFrameY = dialogueBackground.height * TRADER_FRAME_OFFSET_Y;
	var tradeSymbols = self.addChild(new Container());
	var tradeArrow = tradeSymbols.attachAsset('arrow', {
		anchorX: 0.5,
		anchorY: 0.5,
		scaleX: 1.4,
		scaleY: 1.4,
		rotation: -MATH_HALF_PI
	});
	var tradeSell = createTradePart(trade.sellName, trade.sellAmount, {
		x: -tradeArrow.height * 0.7 - TRADER_DETAIL_MARGIN,
		anchorX: 1
	});
	var tradeBuy = createTradePart(trade.buyName, trade.buyAmount, {
		x: tradeArrow.height * 0.7 + TRADER_DETAIL_MARGIN,
		anchorX: 0
	});
	self.addChild(new TraderButtons(callback, {
		x: -(dialogueBackground.width * TRADER_FRAME_OFFSET_X + TRADER_DETAIL_MARGIN) * flipX,
		y: centerFrameY,
		anchorX: 0.5 + 0.5 * flipX
	}));
	tradeSymbols.x = centerFrameX;
	tradeSymbols.y = centerFrameY;
	;
	self.setTint = setTint;
	;
	function setTint(tint) {
		LK.effects.flashObject(dialogueBackground, tint, 500);
	}
	function createTradePart(name, amount, config) {
		if (name === 'credits') {
			return tradeSymbols.addChild(new SymbolText(amount, 'currencySymbol', {
				x: config.x,
				anchorX: config.anchorX,
				anchorY: 0.5,
				tint: true
			}));
		} else {
			return tradeSymbols.addChild(new SymbolText(amount + '×', name, {
				x: config.x,
				anchorX: config.anchorX,
				anchorY: 0.5,
				suffix: true,
				scale: SCALE_FRUIT_TRADE
			}));
		}
	}
	;
	return self;
});
var TraderButtons = ConfigContainer.expand(function (callback, config) {
	var self = ConfigContainer.call(this, config);
	var checkButton = self.addChild(new BasicButton('check', function () {
		callback(true);
	}, {
		y: -TRADER_DETAIL_MARGIN / 2,
		anchorX: config.anchorX,
		anchorY: 1
	}));
	var crossButton = self.addChild(new BasicButton('cross', function () {
		callback(false);
	}, {
		y: TRADER_DETAIL_MARGIN / 2,
		anchorX: config.anchorX,
		anchorY: 0
	}));
	;
	return self;
});
/**** 
* Initialize Game
****/
var game = new LK.Game({
	backgroundColor: 0x000000 // Init game with black background
});
/**** 
* Game Code
****/
;
//==============================================================================
// Global constants & settings
//==============================================================================
;
// Math constants / pre-calculations
var MATH_2_PI = Math.PI * 2;
var MATH_HALF_PI = Math.PI / 2;
var MATH_QUARTER_PI = Math.PI / 4;
var MATH_HALF_ROOT_3 = Math.sqrt(3) / 2; // Required by: TEXT_OFFSETS, BorderedText, BorderedSymbol, SymbolText
var MATH_APPROX_ZERO = 0.0000001;
;
// Text settings
var TEXT_OFFSETS = [[0, 1], [MATH_HALF_ROOT_3, 0.5], [MATH_HALF_ROOT_3, -0.5], [0, -1], [-MATH_HALF_ROOT_3, -0.5], [-MATH_HALF_ROOT_3, 0.5], [0, 0]]; // Required by: BorderedText, BorderedSymbol, SymbolText
var TEXT_BORDER_WEIGHT = 4; // Required by: BorderedText, BorderedSymbol, SymbolText
var TEXT_DEFAULT_BORDER = '#000000'; // Required by: BorderedText, BorderedSymbol, SymbolText
var TEXT_DEFAULT_FILL = '#FFFFFF'; // Required by: BorderedText, SymbolText
var TEXT_DEFAULT_FONT = 'Arial'; // Required by: BorderedText, SymbolText
var TEXT_DEFAULT_SIZE = 50; // Required by: BorderedText, SymbolText
var TEXT_DEFAULT_MARGIN = 0; // Required by: SymbolText
;
// Game constants
var GAME_TESTING = false;
var GAME_TICKS = 60;
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var GAME_SPEED = 0.1;
;
// Rocket constants
var ROCKET_DIST_REVERSE = 300;
var ROCKET_DIST_LEAVE = 2500;
var ROCKET_SPEED_BASE = 1.2;
var ROCKET_SPEED_DIV = 5;
var ROCKET_SPEED_DELAY = 10;
var ROCKET_SPEED_REVERSE = 2;
var ROCKET_FLAME_BREDTH = 30;
var ROCKET_FLAME_LIFETIME = 30;
var ROCKET_FLAME_SPEED = 5;
;
// Farm constants
var PLOT_SIZE = 80;
var PLOT_GAP = 120;
var PLOT_NODE_OFFSET = 20;
var PLOT_FARM_OFFSET = -30;
var PLOT_ALPHA_STEP = 1 / GAME_TICKS;
var PLOT_BUTTON_OFFSET = -220;
;
// Interface settings
var SCALE_FRUIT_DROP = 0.8;
var SCALE_FRUIT_INVENTORY = 0.6;
var SCALE_FRUIT_TRADE = 0.65;
var SCALE_FRUIT_PLANTER = 0.25;
var TEXT_WINNING_OFFSET = 300;
var TEXT_SIZE_SMALL = 35;
var TEXT_SIZE_LARGE = 75;
var CROSSHAIR_DIST = 40;
var CROSSHAIR_VARIANCE = 10;
var CROSSHAIR_PERIOD = 1.25 * GAME_TICKS / MATH_2_PI;
var POPUP_OFFSET = 100;
var POPUP_DURATION = 1.5 * GAME_TICKS;
var POPUP_RECYCLE = Math.floor(POPUP_DURATION * 0.5);
;
// Inventory settings
var INVENTORY_ROWS = 1;
var INVENTORY_COLS = 7;
var INVENTORY_ICON_SCALE = 0.55;
var INVENTORY_SLOT_SIZE = 115;
;
// Asteroid settings
var ASTEROID_SPAWN_CHANCE = 0.001;
var ASTEROID_DROP_CHANCE = 0.1; // 10% chance
var ASTEROID_ROTATION_MAX = 0.02;
var ASTEROID_SPEED = 5;
var ASTEROID_MARGIN = 100;
var ASTEROID_SCALE_MIN = 0.75;
var ASTEROID_SCALE_VAR = 0.5;
var ASTEROID_TARGET_OFFSET = 500;
var EXPLOSION_FRAMES = 15; // Number of frames for the explosion animation to last
;
// Player settings
var PLAYER_SPEED = 5;
var PLAYER_SPAWN_DELAY = 500;
var PLAYER_ACTION_INTERVAL = Math.floor(GAME_TICKS / 4);
var PLAYER_ACTION_DIST = 80;
var PLAYER_ACTION_SQRDIST = PLAYER_ACTION_DIST * PLAYER_ACTION_DIST;
var PLAYER_BUFFER_DIST = 400;
var PLAYER_BUFFER_SQRDIST = PLAYER_BUFFER_DIST * PLAYER_BUFFER_DIST;
var PLAYER_START_ANGLE = Math.PI / 16;
var PLAYER_DISTANCE_SCALE = 0.01;
;
// Trader settings
var TRADER_TIME_INITIAL = 10 * GAME_TICKS;
var TRADER_TIME_MIN = 1 * GAME_TICKS;
var TRADER_TIME_MAX = 30 * GAME_TICKS;
var TRADER_COST_VARIANCE = 0.1;
var TRADER_INVENTORY_MIN = 0.25;
var TRADER_INVENTORY_MAX = 1.5;
var TRADER_MINIMUM_BUY = 5;
var TRADER_FRUIT_OFFER_MIN = 1;
var TRADER_FRUIT_OFFER_MAX = 3;
var TRADER_EXCHANGE_MULTIPLIER = 5;
var TRADER_FRAME_OFFSET_X = 0.1;
var TRADER_FRAME_OFFSET_Y = -0.56;
var TRADER_DETAIL_MARGIN = 20;
var TRADER_SHIP_SPACING = 50;
var TRADER_MOVE_INTERVAL = 20;
var TRADER_MOVE_ROTATION = 0.1;
var TRADER_MOVE_ANCHOR = 0.1;
var TRADER_SPAWN_TIME = 2 * GAME_TICKS;
var TRADER_SPAWN_RADIUS = 1000;
;
// Plant settings
var WEEDS_SPAWN_CHANCE = 0.6;
var WEEDS_SPAWN_TIME = 5 * 60 * GAME_TICKS;
var WEEDS_SPAWN_VARIANCE = 2 * 60 * GAME_TICKS;
var WEEDS_SPREAD_TIME = 1 * 60 * GAME_TICKS;
var WEEDS_SPREAD_VARIANCE = 20 * GAME_TICKS;
var GROW_WARNING_BARREN = 'Nothing grows on this planet';
var GROW_WARNING_GENERAL = 'This cannot grow on this planet';
var GROW_WARNING_DIAMOND = 'This cannot be planted, only sold';
var FRUIT_FALL_SPEED = 3;
var PLANT_SCALE_VARIANCE = 0.1;
var PLANT_GROWTH_FACTOR = 0.2;
var PLANT_DEAD_COLOUR = 0xA3784B;
var PLANT_DETAILS = {
	plantWeeds: {
		blueprint: PlantWeeds,
		stages: 4,
		fruit: 'fruitWeeds',
		growthTime: 60 * GAME_TICKS,
		growthVariance: 10 * GAME_TICKS
	},
	plantBush: {
		blueprint: PlantBush,
		stages: 6,
		fruit: 'fruitBush',
		growthTime: 40 * GAME_TICKS,
		growthVariance: 10 * GAME_TICKS
	},
	plantStalk: {
		blueprint: PlantStalk,
		stages: 7,
		fruit: 'fruitStalk',
		growthTime: 50 * GAME_TICKS,
		growthVariance: 15 * GAME_TICKS
	},
	plantEyeball: {
		blueprint: PlantEyeball,
		stages: 5,
		fruit: 'fruitEyeball',
		growthTime: 35 * GAME_TICKS,
		growthVariance: 20 * GAME_TICKS
	},
	plantFlower: {
		blueprint: PlantFlower,
		stages: 7,
		fruit: 'fruitFlower',
		growthTime: 60 * GAME_TICKS,
		growthVariance: 30 * GAME_TICKS
	},
	plantDiamond: {
		blueprint: PlantDiamond,
		stages: 4,
		fruit: 'fruitDiamond',
		growthTime: 100 * GAME_TICKS,
		growthVariance: 30 * GAME_TICKS
	}
};
var FRUIT_DETAILS = {
	fruitWeeds: {
		value: 1,
		type: 'green',
		plant: 'plantWeeds'
	},
	fruitBush: {
		value: 5,
		type: 'green',
		plant: 'plantBush'
	},
	fruitStalk: {
		value: 5,
		type: 'red',
		plant: 'plantStalk'
	},
	fruitEyeball: {
		value: 10,
		type: 'red',
		plant: 'plantEyeball'
	},
	fruitFlower: {
		value: 15,
		type: 'blue',
		plant: 'plantFlower'
	},
	fruitDiamondDust: {
		value: 0,
		type: 'omni',
		plant: 'plantDiamond'
	},
	fruitDiamond: {
		value: 250,
		minSell: 1
	}
};
;
// Planet & navigation settings
var NAVIGATION = ['planetGrey', 'planetRed', 'planetBlue', 'planetOmni', 'planetGold'];
var PLANET_LAST = NAVIGATION[NAVIGATION.length - 1];
var PLANET_DETAILS = {
	planetGrey: {
		cost: 0,
		radius: 250,
		spin: 0.001,
		blueprint: PlanetGrey,
		plantTypes: ['green'],
		buy: ['credits', 'credits', 'fruitBush', 'fruitStalk'],
		sell: ['credits', 'fruitWeeds', 'fruitBush']
	},
	planetRed: {
		cost: 75,
		radius: 300,
		spin: -0.0002,
		blueprint: PlanetRed,
		plantTypes: ['green', 'red'],
		buy: ['credits', 'credits', 'fruitStalk', 'fruitEyeball'],
		sell: ['credits', 'fruitWeeds', 'fruitBush', 'fruitStalk', 'fruitEyeball']
	},
	planetBlue: {
		cost: 250,
		radius: 250,
		spin: 0.001,
		blueprint: PlanetBlue,
		plantTypes: ['green', 'blue'],
		buy: ['credits', 'credits', 'fruitFlower'],
		sell: ['credits', 'fruitWeeds', 'fruitBush', 'fruitFlower']
	},
	planetOmni: {
		cost: 1000,
		radius: 350,
		spin: -0.0005,
		coarse: true,
		blueprint: PlanetOmni,
		plantTypes: ['green', 'blue', 'red', 'omni'],
		buy: ['credits'],
		sell: ['fruitStalk', 'fruitEyeball', 'fruitFlower', 'fruitDiamond', 'fruitDiamond']
	},
	planetGold: {
		cost: 3250,
		radius: 150,
		spin: -0.002,
		barren: true,
		blueprint: PlanetGold,
		plantTypes: [],
		buy: [],
		sell: []
	}
};
;
//==============================================================================
// Global variables
//==============================================================================
;
var money = 25;
var planetMap = {};
var asteroidList = [];
var effectsList = [];
var popupMap = {};
var stats = {
	creditsEarned: 0,
	cropsPlanted: 0,
	cropsHarvested: 0,
	cropsOverrun: 0,
	weedsPulled: 0,
	salesDone: 0,
	salesRejected: 0,
	distanceRun: 0,
	rocketLaunches: 0,
	asteroidImpacts: 0
};
var winningStats = {};
var winningTick = -1;
var winningTime = '';
var winningMessage;
var player;
var traders = [];
var traderCountdown = TRADER_TIME_INITIAL;
var nextTrade = {
	buyName: 'fruitBush',
	buyAmount: 2,
	sellName: 'fruitWeeds',
	sellAmount: 9 + Math.round(2 * Math.random())
};
var skipRetarget = false;
var retargetAngle;
var targetAngle = PLAYER_START_ANGLE;
var taskLaunch = false;
var taskPlanter = false;
var taskUproot = false;
var currentPlanet = NAVIGATION[0];
var destinationPlanet = NAVIGATION[0];
var background = game.addChild(new Background());
var midground = game.addChild(new Container());
var foreground = game.addChild(new Container());
var planet = midground.addChild(new PlanetGrey({
	x: GAME_WIDTH / 2,
	y: GAME_HEIGHT / 2,
	rotation: Math.random() * MATH_2_PI
}));
var ship = planet.background.addChild(new Ship(planet.radius + ROCKET_DIST_REVERSE, 0));
var inventory = LK.gui.top.addChild(new Inventory({
	y: INVENTORY_SLOT_SIZE / 2 + 10,
	anchorX: .5,
	anchorY: .5
}));
var moneyDisplay = LK.gui.top.addChild(new SymbolText(money, 'currencySymbol', {
	x: inventory.width / 2 + 25,
	y: inventory.y,
	tint: true,
	anchorX: 0,
	anchorY: .5
}));
var crosshair = new Crosshair();
var navigation = LK.gui.bottom.addChild(new NavigationInterface({
	x: 0,
	y: -100
}));
planetMap[planet.name] = planet;
// Adjustments made purely for testing purposes
if (GAME_TESTING) {
	money = 50000;
	PLANT_GROWTH_FACTOR = 0.1;
	inventory.adjustItem('fruitWeeds', 10);
	inventory.adjustItem('fruitBush', 10);
	inventory.adjustItem('fruitStalk', 10);
	inventory.adjustItem('fruitEyeball', 10);
	inventory.adjustItem('fruitFlower', 10);
	inventory.adjustItem('fruitDiamondDust', 10);
}
;
//==============================================================================
// Gameplay events
//==============================================================================
;
LK.on('tick', function () {
	if (skipRetarget) {
		skipRetarget = false;
	} else if (retargetAngle !== undefined) {
		targetAngle = retargetAngle;
	}
	retargetAngle = undefined;
	if (player) {
		player.update();
	}
	ship.update();
	crosshair.update();
	updateList(effectsList);
	for (var key in planetMap) {
		planetMap[key].update();
	}
	for (var i = 0; i < 4; i++) {
		var trader = traders[i];
		if (trader) {
			trader.update();
		}
	}
	if (planet.name !== PLANET_LAST) {
		updateList(asteroidList);
		trySpawnAsteroid();
		trySpawnTrader();
	}
});
;
game.on('down', function (obj) {
	if (player) {
		var clickPosition = obj.event.getLocalPosition(game);
		var dx = clickPosition.x - planet.x;
		var dy = clickPosition.y - planet.y;
		var range = planet.radius + PLAYER_BUFFER_DIST;
		if (dx * dx + dy * dy <= range * range) {
			resetTasks();
			retargetAngle = Math.atan2(dy, dx) - planet.rotation;
		}
	}
});
;
//==============================================================================
// Global functions
//==============================================================================
;
function updateList(list, idle) {
	for (var i = list.length - 1; i >= 0; i--) {
		var item = list[i];
		if (item.update && item.update(idle)) {
			item.destroy();
			list.splice(i, 1);
		}
	}
}
;
function trySpawnAsteroid() {
	if (Math.random() < ASTEROID_SPAWN_CHANCE) {
		var side = Math.random() < 0.5;
		asteroidList.push(midground.addChild(new Asteroid({
			x: side ? -ASTEROID_MARGIN : GAME_WIDTH + ASTEROID_MARGIN,
			y: Math.random() * GAME_HEIGHT,
			vx: side ? ASTEROID_SPEED : -ASTEROID_SPEED
		})));
	}
}
;
function trySpawnTrader() {
	if (--traderCountdown <= 0) {
		traderCountdown = randomInt(TRADER_TIME_MIN, TRADER_TIME_MAX);
		var available = [];
		for (var i = 0; i < 4; i++) {
			if (!traders[i]) {
				available.push(i);
			}
		}
		if (available.length > 0) {
			var spawnIndex = available[Math.floor(Math.random() * available.length)];
			traders[spawnIndex] = background.addChild(new Trader({
				index: spawnIndex,
				x: GAME_WIDTH / 4 * (spawnIndex % 2 === 0 ? 1 : 3),
				y: GAME_HEIGHT / 32 * (spawnIndex < 2 ? 9 : 27)
			}, nextTrade));
			nextTrade = undefined;
		}
	}
}
;
function transitionPlanets() {
	LK.effects.flashScreen(0x000000, 500);
	currentPlanet = destinationPlanet;
	planet.parent.removeChild(planet);
	planet = planetMap[currentPlanet];
	stats.rocketLaunches++;
	asteroidList.forEach(function (asteroid) {
		asteroid.destroy();
	});
	asteroidList = [];
	for (var i = 0; i < 4; i++) {
		var trader = traders[i];
		if (trader) {
			trader.handleTradeCallback(false);
			trader.destroy();
		}
	}
	traders = [];
	if (!planet) {
		var planetClass = PLANET_DETAILS[currentPlanet].blueprint;
		planet = planetMap[currentPlanet] = new planetClass({
			x: GAME_WIDTH / 2,
			y: GAME_HEIGHT / 2
		});
	}
	if (currentPlanet === NAVIGATION[NAVIGATION.length - 1]) {
		if (winningTick < 0) {
			saveStats();
			winningTick = LK.ticks;
		}
		winningMessage = LK.gui.center.addChild(new WinningMessage(0, -100, winningTick));
	} else if (winningMessage) {
		winningMessage.destroy();
		winningMessage = undefined;
	}
	midground.addChild(planet);
	planet.background.addChild(ship);
	ship.x = planet.radius + ROCKET_DIST_REVERSE;
	resetTasks();
	targetAngle = PLAYER_START_ANGLE;
	background.refresh();
	inventory.refreshAllowed();
}
;
function resetTasks() {
	if (taskLaunch) {
		taskLaunch = false;
		navigation.setDestination(currentPlanet);
	}
	taskUproot = false;
	taskPlanter = false;
	if (player) {
		targetAngle = player.angle;
	}
}
;
function saveStats() {
	for (var key in stats) {
		winningStats[key] = stats[key];
	}
}
;
function mod(x, base) {
	return (x % base + base) % base;
}
;
// TODO: Remove
function randomInt(min, max) {
	return min === max ? min : Math.floor(min + Math.random() * (max - min));
}
function randomIntInRange(min, max) {
	return min === max ? min : Math.floor(min + Math.random() * (max - min));
}
function randomFloatInRange(min, max) {
	return min === max ? min : min + Math.random() * (max - min);
}
;
function approxZero(value) {
	return value > -MATH_APPROX_ZERO && value < MATH_APPROX_ZERO;
} /**** 
* Classes
****/
var ConfigContainer = Container.expand(function (config) {
	var self = Container.call(this);
	config = config || {};
	;
	self.x = config.x || 0;
	self.y = config.y || 0;
	self.rotation = config.rotation || 0;
	;
	return self;
});
/** 
* var config = {
* 	x        : Number || 0, // See: ConfigContainer
* 	y        : Number || 0, // See: ConfigContainer
* 	rotation : Number || 0, // See: ConfigContainer
* 	anchorX  : Number || 0,
* 	anchorY  : Number || 1,
* 	size     : Number || TEXT_DEFAULT_SIZE,
* 	font     : String || TEXT_DEFAULT_FONT,
* 	fill     : String || TEXT_DEFAULT_FILL,
* 	border   : String || TEXT_DEFAULT_BORDER,
* }
**/
var BorderedText = ConfigContainer.expand(function (text, config) {
	var self = ConfigContainer.call(this, config);
	config = config || {};
	var anchorX = config.anchorX !== undefined ? config.anchorX : 0;
	var anchorY = config.anchorY !== undefined ? config.anchorY : 1;
	var size = config.size !== undefined ? config.size : TEXT_DEFAULT_SIZE;
	var font = config.font !== undefined ? config.font : TEXT_DEFAULT_FONT;
	var textFill = config.fill !== undefined ? config.fill : TEXT_DEFAULT_FILL;
	var borderFill = config.border !== undefined ? config.border : TEXT_DEFAULT_BORDER;
	var mainAsset;
	var textAssets = [];
	;
	self.setText = setText;
	self.setFill = setFill;
	;
	function setText(newText) {
		for (var i = 0; i < textAssets.length; i++) {
			textAssets[i].setText(newText);
		}
	}
	function setFill(newFill) {
		textFill = newFill;
		mainAsset.fill = newFill;
	}
	function buildTextAssets(newText) {
		for (var i = 0; i < TEXT_OFFSETS.length; i++) {
			var main = i === TEXT_OFFSETS.length - 1;
			var textAsset = textAssets[i];
			if (textAsset) {
				textAsset.destroy();
			}
			textAsset = self.addChild(new Text2(newText, {
				fill: main ? textFill : borderFill,
				font: font,
				size: size
			}));
			textAsset.anchor = {
				x: anchorX,
				y: anchorY
			}; // NOTE: Cannot be set in config
			textAsset.x = TEXT_OFFSETS[i][0] * TEXT_BORDER_WEIGHT; // NOTE: Cannot be set in config
			textAsset.y = TEXT_OFFSETS[i][1] * TEXT_BORDER_WEIGHT; // NOTE: Cannot be set in config
			textAssets[i] = textAsset;
		}
		mainAsset = textAssets[TEXT_OFFSETS.length - 1];
	}
	;
	buildTextAssets(text);
	return self;
});
/** 
* var config = {
* 	x        : Number || 0,         // See: ConfigContainer
* 	y        : Number || 0,         // See: ConfigContainer
* 	rotation : Number || 0,         // See: ConfigContainer
* 	anchorX  : Number || .5,
* 	anchorY  : Number || .5,
* 	scale    : Number || undefined,
* 	scaleX   : Number || scale,
* 	scaleY   : Number || scale,
* 	width    : Number || undefined, // Auto-calculated if left undefined and height is set
* 	height   : Number || undefined, // Auto-calculated if left undefined and width is set
* 	tint     : String || 0xFFFFFF,  // Not tinted by default
* 	border   : String || TEXT_DEFAULT_BORDER,
* }
**/
var BorderedSymbol = ConfigContainer.expand(function (symbol, config) {
	var self = ConfigContainer.call(this, config);
	config = config || {};
	var width = config.width !== undefined ? config.width : undefined;
	var height = config.height !== undefined ? config.height : undefined;
	var scale = config.scale !== undefined ? config.scale : undefined;
	var scaleX = config.scaleX !== undefined ? config.scaleX : scale;
	var scaleY = config.scaleY !== undefined ? config.scaleX : scale;
	var anchorX = config.anchorX !== undefined ? config.anchorX : .5;
	var anchorY = config.anchorY !== undefined ? config.anchorY : .5;
	var symbolTint = config.tint !== undefined ? config.tint : 0xFFFFFF;
	var borderTint = config.border !== undefined ? config.border : TEXT_DEFAULT_BORDER;
	var mainSymbol;
	var symbolAssets = [];
	;
	self.setSymbol = buildSymbolAssets;
	self.setTint = setTint;
	;
	function setTint(newTint) {
		// NOTE: Tinting is currently broken (cannot use string)
		// mainSymbol.tint = newTint;
		// symbolConfig.tint = newTint;
	}
	function buildSymbolAssets(newSymbol) {
		for (var i = 0; i < TEXT_OFFSETS.length; i++) {
			var main = i === TEXT_OFFSETS.length - 1;
			var symbolAsset = symbolAssets[i];
			if (symbolAsset) {
				symbolAsset.destroy();
			}
			symbolAsset = self.attachAsset(newSymbol, {
				// tint: main ? symbolTint : borderTint,
				tint: main ? 0xFFFFFF : 0x000000,
				// NOTE: Tinting is currently broken (cannot use string)
				x: TEXT_OFFSETS[i][0] * TEXT_BORDER_WEIGHT,
				y: TEXT_OFFSETS[i][1] * TEXT_BORDER_WEIGHT,
				anchorX: anchorX,
				anchorY: anchorY
			});
			if (width !== undefined && height === undefined) {
				height = symbolAsset.height * (width / symbolAsset.width);
			} else if (height !== undefined && width === undefined) {
				width = symbolAsset.width * (height / symbolAsset.height);
			}
			symbolAsset.width = width;
			symbolAsset.height = height;
			if (scaleX !== undefined && scaleY !== undefined) {
				symbolAsset.scale.set(scaleX, scaleY);
			}
			symbolAssets[i] = symbolAsset;
		}
		mainSymbol = symbolAssets[TEXT_OFFSETS.length - 1];
	}
	;
	buildSymbolAssets(symbol);
	return self;
});
/** 
* var config = {
* 	x        : Number  || 0,         // See: ConfigContainer
* 	y        : Number  || 0,         // See: ConfigContainer
* 	rotation : Number  || 0,         // See: ConfigContainer
* 	prefix   : Boolean || true,
* 	suffix   : Boolean || false,     // Overriden by prefix
* 	tint     : Boolean || false,     // Whether to tint the symbol
* 	anchorX  : Number  || 0,
* 	anchorY  : Number  || 1,
* 	margin   : Number  || TEXT_DEFAULT_MARGIN,
* 	size     : Number  || TEXT_DEFAULT_SIZE,
* 	scale    : Number  || undefined, // See: BorderedSymbol
* 	fill     : String  || undefined, // See: BorderedText, BorderedSymbol (tint)
* 	border   : Number  || undefined, // See: BorderedText, BorderedSymbol
* }
**/
var SymbolText = ConfigContainer.expand(function (text, symbol, config) {
	var self = ConfigContainer.call(this, config);
	config = config || {};
	var prefix = !!config.prefix || !config.suffix;
	var tint = !!config.tint;
	var anchorX = config.anchorX !== undefined ? config.anchorX : 0;
	var anchorY = config.anchorY !== undefined ? config.anchorY : 1;
	var margin = config.margin !== undefined ? config.margin : TEXT_DEFAULT_MARGIN;
	var size = config.size !== undefined ? config.size : TEXT_DEFAULT_SIZE;
	var symbolHeight = config.scale === undefined ? size : undefined;
	var textAsset = self.addChild(new BorderedText(text, {
		anchorX: 0,
		anchorY: 0.5,
		font: config.font,
		// Passthrough
		fill: config.fill,
		// Passthrough
		border: config.border,
		// Passthrough
		size: size
	}));
	var symbolAsset = self.addChild(new BorderedSymbol(symbol, {
		anchorX: 0,
		anchorY: 0.5,
		height: symbolHeight,
		tint: tint ? config.fill : undefined,
		border: config.border,
		// Passthrough
		scale: config.scale
	}));
	;
	self.setText = setText;
	self.setFill = setFill;
	self.setSymbol = setSymbol;
	;
	function alignAssets() {
		var first = prefix ? symbolAsset : textAsset;
		var second = prefix ? textAsset : symbolAsset;
		var totalWidth = symbolAsset.width + margin + textAsset.width;
		var heightOffset = Math.max(first.height, second.height) * (0.5 - anchorY);
		first.x = -totalWidth * anchorX;
		first.y = heightOffset;
		second.x = first.x + first.width + margin;
		second.y = heightOffset;
	}
	function setText(newText) {
		textAsset.setText(newText);
		alignAssets();
	}
	function setFill(newFill) {
		textAsset.setFill(newFill);
		if (tint) {
			symbolAsset.setTint(newFill);
		}
	}
	function setSymbol(newSymbol) {
		symbolAsset.setSymbol(newSymbol);
		alignAssets();
	}
	;
	alignAssets();
});
var Player = ConfigContainer.expand(function (config) {
	var self = ConfigContainer.call(this, config);
	var actionCountdown = PLAYER_ACTION_INTERVAL;
	var playerGraphics = self.attachAsset('player', {
		anchorX: 0.5,
		anchorY: 0.75
	});
	;
	self.update = update;
	self.angle = 0;
	;
	function update() {
		var currentAngle = Math.atan2(self.y, self.x);
		var angleDifference = mod(targetAngle - currentAngle + Math.PI, MATH_2_PI) - Math.PI;
		if (approxZero(angleDifference)) {
			if (taskLaunch) {
				self.destroy();
				player = undefined;
				LK.setTimeout(function () {
					ship.launch(1);
				}, PLAYER_SPAWN_DELAY);
			} else if (--actionCountdown <= 0) {
				performAction(currentAngle);
				actionCountdown = PLAYER_ACTION_INTERVAL;
			}
		} else {
			var direction = angleDifference / Math.abs(angleDifference);
			var angleStep = PLAYER_SPEED / planet.radius;
			var angleIncrease = Math.min(Math.abs(angleDifference), angleStep);
			var newAngle = currentAngle + direction * angleIncrease;
			self.x = Math.cos(newAngle) * planet.radius;
			self.y = Math.sin(newAngle) * planet.radius;
			self.angle = newAngle;
			stats.distanceRun += angleIncrease * planet.radius;
			playerGraphics.scale.x = direction < 0 ? -1 : 1;
			self.rotation = newAngle + MATH_HALF_PI;
			actionCountdown = 0; // Perform action immediately after stopping
		}
	}
	function performAction(angle) {
		var count = planet.nodes.length;
		var baseIndex = mod(Math.round(angle / MATH_2_PI * count), count);
		var baseNode = planet.nodes[baseIndex];
		if (baseNode && !baseNode.tryAction()) {
			var iterations = Math.floor(PLAYER_ACTION_DIST / (planet.radius * MATH_2_PI / planet.nodes.length));
			var prev = baseNode.prev;
			var next = baseNode.next;
			while (iterations > 0) {
				iterations--;
				if (next && next.tryAction()) {
					break;
				}
				if (prev && prev.tryAction()) {
					break;
				}
				prev = prev ? prev.prev : null;
				next = next ? next.next : null;
			}
		}
	}
});
var Ship = Container.expand(function (x, y) {
	var self = Container.call(this);
	var background = self.addChild(new Container());
	var shipGraphics = self.attachAsset('ship', {
		anchorX: 0.10,
		anchorY: 0.5
	});
	var counter = 0;
	var particles = [];
	background.rotation = -MATH_HALF_PI;
	;
	self.update = update;
	self.launch = launch;
	self.x = x;
	self.y = y;
	self.direction = -1;
	;
	function update() {
		if (self.direction < 0) {
			self.x -= ROCKET_SPEED_REVERSE;
			if (self.x <= planet.radius) {
				self.x = planet.radius;
				self.direction = 0;
				LK.setTimeout(function () {
					player = planet.addChild(new Player({
						rotation: ship.rotation,
						x: ship.x,
						y: ship.y
					}));
				}, PLAYER_SPAWN_DELAY);
			}
		} else if (self.direction > 0) {
			counter++;
			var speed = Math.pow(ROCKET_SPEED_BASE, counter / ROCKET_SPEED_DIV - ROCKET_SPEED_DELAY);
			self.x += speed;
			if (self.x > ROCKET_DIST_LEAVE) {
				counter = 0;
				self.direction = -1;
				transitionPlanets();
			}
		}
		if (self.direction) {
			particles.push(background.addChild(new FlameParticle({
				x: (Math.random() - 0.5) * ROCKET_FLAME_BREDTH
			})));
		}
		updateList(particles);
	}
	function launch(direction) {
		if (!self.direction) {
			self.direction = direction;
		}
	}
});
var FlameParticle = ConfigContainer.expand(function (config) {
	var self = ConfigContainer.call(this, config);
	var flameGraphics = self.attachAsset('flame', {
		anchorX: 0.5,
		anchorY: 0.5,
		alpha: 0.5
	});
	var lifetime = ROCKET_FLAME_LIFETIME;
	;
	self.update = update;
	;
	function update() {
		var remaining = lifetime / ROCKET_FLAME_LIFETIME;
		self.y -= ROCKET_FLAME_SPEED;
		flameGraphics.alpha = 0.5 * remaining;
		flameGraphics.scale.set(remaining);
		return --lifetime <= 0;
	}
});
var Plant = ConfigContainer.expand(function (type, growthStage, potted, parentPlanet, config) {
	var self = ConfigContainer.call(this, config);
	var details = PLANT_DETAILS[type];
	var scale = 1 + randomFloatInRange(-PLANT_SCALE_VARIANCE, PLANT_SCALE_VARIANCE);
	;
	self.update = update;
	self.uproot = uproot;
	self.action = action;
	self.harvest = harvest;
	self.updateStage = updateStage;
	self.refreshGraphics = refreshGraphics;
	self.refreshCountdown = refreshCountdown;
	self.type = type;
	self.fruit = details.fruit;
	self.fruitCount = 0;
	self.potted = potted;
	self.graphics;
	self.growthStage = growthStage;
	self.growthStages = details.stages;
	self.growthTime = details.growthTime;
	self.growthVariance = details.growthVariance;
	self.growthCountdown = 0;
	self.skipFinalGrowth = false;
	self.fruitOffsetMin = 0;
	self.fruitOffsetMax = 0;
	self.fruitRange = 0;
	self.harvest;
	self.strong = false; // Whether the plant can prevent weeds overrunning it
	self.actionable = false; // Whether passive actions can be performed on this plant
	self.uprootable = false; // Whether the plant can be manually uprooted (with the uproot button)
	;
	function update() {
		if (--self.growthCountdown <= 0 && self.growthStage < self.growthStages) {
			self.updateStage(self.growthStage + 1);
		}
	}
	function uproot(overrun) {
		if (overrun) {
			if (!self.strong) {
				stats.cropsOverrun++;
				self.harvest();
				return true;
			}
		} else if (self.uprootable) {
			self.harvest();
			return true;
		}
		return false;
	}
	function action() {
		return self.actionable;
	}
	function harvest(quantity) {
		var max = Math.min(self.fruitCount, quantity || Infinity);
		for (var i = 0; i < max; i++) {
			self.fruitCount--;
			var fruitOffset = randomFloatInRange(self.fruitOffsetMin, self.fruitOffsetMax);
			var rotation = self.parent.rotation;
			var fruitX = self.parent.x + Math.cos(rotation + MATH_HALF_PI) * (self.y - fruitOffset);
			var fruitY = self.parent.y + Math.sin(rotation + MATH_HALF_PI) * (self.y - fruitOffset);
			if (self.fruitRange > 0) {
				var rangeRotation = Math.random() * MATH_2_PI;
				var rangeDistance = Math.random() * self.fruitRange;
				fruitX += Math.cos(rangeRotation) * rangeDistance;
				fruitY += Math.sin(rangeRotation) * rangeDistance;
			}
			new Fruit(self.fruit, parentPlanet, {
				x: fruitX,
				y: fruitY,
				rotation: rotation
			});
		}
	}
	function updateStage(newStage) {
		if (newStage !== self.growthStages || !self.skipFinalGrowth) {
			self.growthStage = newStage;
			self.refreshGraphics();
		}
		if (self.growthStage !== self.growthStages) {
			self.refreshCountdown();
		}
	}
	function refreshGraphics() {
		if (self.graphics) {
			self.graphics.destroy();
		}
		self.graphics = self.createAsset(self.type + self.growthStage, {
			anchorX: 0.5,
			anchorY: 1,
			scaleX: scale * Math.random() < 0.5 ? 1 : -1,
			scaleY: scale
		});
	}
	function refreshCountdown() {
		var baseTime = self.growthTime + Math.random() * self.growthVariance;
		self.growthCountdown = Math.floor(PLANT_GROWTH_FACTOR * baseTime);
	}
	;
	return self;
});
var PlantWeeds = Plant.expand(function (growthStage, potted, parentPlanet, config) {
	var self = Plant.call(this, 'plantWeeds', growthStage, potted, parentPlanet, config);
	var baseUpdate = self.update;
	var baseHarvest = self.harvest;
	var baseUpdateStage = self.updateStage;
	var spreadCountdown = 0;
	;
	self.update = update;
	self.action = action;
	self.harvest = harvest;
	self.updateStage = updateStage;
	self.strong = true;
	self.actionable = !potted;
	self.uprootable = potted;
	;
	function update() {
		baseUpdate();
		;
		if (self.growthStage === self.growthStages && --spreadCountdown <= 0) {
			var node = self.parent;
			var nextPlant = node.next.plant;
			var prevPlant = node.prev.plant;
			var nextSpreadable = !nextPlant || !nextPlant.strong;
			var prevSpreadable = !prevPlant || !prevPlant.strong;
			if (nextSpreadable || prevSpreadable) {
				var targetNode;
				if (nextSpreadable && prevSpreadable) {
					targetNode = Math.random() < 0.5 ? node.next : node.prev;
				} else {
					targetNode = nextSpreadable ? node.next : node.prev;
				}
				targetNode.addPlant(self.type, 1, true);
			}
			refreshCountdown();
		}
	}
	function action() {
		if (self.actionable) {
			self.harvest();
			self.parent.removePlant();
			return true;
		}
		return false;
	}
	function harvest(quantity) {
		baseHarvest(quantity);
		;
		stats.weedsPulled++;
	}
	function updateStage(newStage) {
		baseUpdateStage(newStage);
		;
		if (newStage === self.growthStages) {
			self.fruitCount = 2;
			self.fruitOffsetMin = 85;
			self.fruitOffsetMax = 95;
		} else if (newStage === self.growthStages - 1) {
			self.fruitCount = 1;
			self.fruitOffsetMin = 35;
			self.fruitOffsetMax = 45;
		}
	}
	function refreshCountdown() {
		var baseTime = WEEDS_SPREAD_TIME + Math.random() * WEEDS_SPREAD_VARIANCE;
		spreadCountdown = Math.floor(PLANT_GROWTH_FACTOR * baseTime);
	}
	;
	self.updateStage(growthStage);
});
var PlantBush = Plant.expand(function (growthStage, potted, parentPlanet, config) {
	var self = Plant.call(this, 'plantBush', growthStage, potted, parentPlanet, config);
	var baseUpdateStage = self.updateStage;
	var baseHarvest = self.harvest;
	var maxFruitCount = 3;
	var fruitRemaining = 6;
	var fruitScale = 0.8;
	var fruitYFactor = 0.5;
	var fruitAssets = [];
	;
	self.updateStage = updateStage;
	self.harvest = harvest;
	self.action = action;
	self.skipFinalGrowth = true;
	self.fruitOffsetMin = 45;
	self.fruitOffsetMax = 45;
	self.fruitRange = 30;
	;
	function updateStage(newStage) {
		baseUpdateStage(newStage);
		;
		if (newStage === self.growthStages) {
			self.strong = true;
			if (!self.skipFinalGrowth) {
				self.graphics.tint = PLANT_DEAD_COLOUR;
				self.uprootable = true;
				self.actionable = false;
				self.harvest();
			}
			if (fruitRemaining-- <= 0) {
				self.skipFinalGrowth = false;
			} else if (self.fruitCount < maxFruitCount) {
				self.fruitCount++;
				self.actionable = true;
				updateVisibleFruit();
			}
		}
	}
	function harvest(quantity) {
		baseHarvest(quantity);
		;
		self.actionable = self.fruitCount > 0;
		updateVisibleFruit();
	}
	function action() {
		if (self.actionable && self.fruitCount > 0) {
			self.harvest(1);
			return true;
		}
		return false;
	}
	function updateVisibleFruit() {
		var assetSlots = [];
		var emptySlots = [];
		for (var i = 0; i < maxFruitCount; i++) {
			(fruitAssets[i] ? assetSlots : emptySlots).push(i);
		}
		var change = self.fruitCount - assetSlots.length;
		while (change !== 0) {
			if (change < 0) {
				// Remove asset
				var index = randomIntInRange(0, assetSlots.length);
				var slot = assetSlots[index];
				fruitAssets[slot].destroy();
				fruitAssets[slot] = undefined;
				assetSlots.splice(index, 1);
				change++;
			} else if (change > 0) {
				// Make new asset
				var index = randomIntInRange(0, emptySlots.length);
				var slot = emptySlots[index];
				var offset = slot - (maxFruitCount / 2 - 0.5);
				var fruitX = offset * self.fruitRange;
				var fruitY = -randomFloatInRange(self.fruitOffsetMin, self.fruitOffsetMax) - Math.cos(offset * MATH_HALF_PI) * self.fruitRange * fruitYFactor;
				fruitAssets[slot] = self.attachAsset(self.fruit, {
					x: fruitX,
					y: fruitY,
					anchorX: 0.5,
					anchorY: 0.5,
					scaleX: fruitScale,
					scaleY: fruitScale,
					rotation: -MATH_QUARTER_PI
				});
				emptySlots.splice(index, 1);
				change--;
			}
		}
	}
	;
	self.updateStage(growthStage);
	return self;
});
var PlantStalk = Plant.expand(function (growthStage, potted, parentPlanet, config) {
	var self = Plant.call(this, 'plantStalk', growthStage, potted, parentPlanet, config);
	var baseUpdateStage = self.updateStage;
	var yieldStage = 5;
	var yieldPerStage = 3;
	;
	self.updateStage = updateStage;
	self.skipFinalGrowth = true;
	self.fruitOffsetMin = 200;
	self.fruitOffsetMax = 300;
	self.fruitRange = 30;
	;
	function updateStage(newStage) {
		baseUpdateStage(newStage);
		;
		if (newStage >= yieldStage) {
			self.fruitCount += yieldPerStage;
		}
		if (newStage === self.growthStages) {
			self.harvest();
			self.parent.removePlant();
		}
	}
	;
	self.updateStage(growthStage);
	return self;
});
var PlantEyeball = Plant.expand(function (growthStage, potted, parentPlanet, config) {
	var self = Plant.call(this, 'plantEyeball', growthStage, potted, parentPlanet, config);
	var baseUpdateStage = self.updateStage;
	var decorAmount = 3;
	var decorScale = 0.5;
	var fruitChance = 0.33;
	var fruitChances = 20;
	;
	self.updateStage = updateStage;
	self.skipFinalGrowth = true;
	self.fruitOffsetMin = 80;
	self.fruitOffsetMax = 140;
	self.fruitRange = 20;
	;
	function updateStage(newStage) {
		baseUpdateStage(newStage);
		;
		if (newStage === self.growthStages) {
			if (fruitChances-- > 0) {
				if (Math.random() < fruitChance) {
					self.harvest(self.fruitCount = 1);
				}
			} else {
				self.harvest(self.fruitCount = decorAmount);
				self.parent.removePlant();
			}
		} else if (newStage === self.growthStages - 1) {
			createVisuals();
		}
	}
	function createVisuals() {
		for (var i = 0; i < decorAmount; i++) {
			var decorOffset = randomFloatInRange(self.fruitOffsetMin, self.fruitOffsetMax);
			var rangeRotation = Math.random() * MATH_2_PI;
			var rangeDistance = Math.random() * self.fruitRange;
			var decorX = self.x + Math.cos(rangeRotation) * rangeDistance;
			var decorY = self.y - decorOffset + Math.sin(rangeRotation) * rangeDistance;
			self.attachAsset(self.fruit, {
				x: decorX,
				y: decorY,
				scaleX: decorScale,
				scaleY: decorScale
			});
		}
	}
	;
	self.updateStage(growthStage);
	return self;
});
var PlantFlower = Plant.expand(function (growthStage, potted, parentPlanet, config) {
	var self = Plant.call(this, 'plantFlower', growthStage, potted, parentPlanet, config);
	var baseUpdateStage = self.updateStage;
	var yieldStage = 4;
	var fruitYFactor = 0.75;
	var fruitCount = 4;
	;
	self.action = action;
	self.updateStage = updateStage;
	self.fruitRange = 50;
	;
	function action() {
		if (self.actionable) {
			self.harvest();
			self.parent.removePlant();
			return true;
		}
		return false;
	}
	function updateStage(newStage) {
		baseUpdateStage(newStage);
		;
		if (newStage === self.growthStages) {
			// Dead plant/flower
			self.fruitCount = Math.round(Math.random());
			self.graphics.tint = PLANT_DEAD_COLOUR;
			self.actionable = false;
			self.uprootable = true;
		} else if (newStage >= yieldStage) {
			// Harvestable plant/flower
			self.fruitCount = fruitCount + Math.round(Math.random());
			self.actionable = true;
			self.fruitOffsetMin = self.graphics.height * fruitYFactor;
			self.fruitOffsetMax = self.graphics.height * fruitYFactor;
		}
	}
	;
	self.updateStage(growthStage);
	return self;
});
var PlantDiamond = Plant.expand(function (growthStage, potted, parentPlanet, config) {
	var self = Plant.call(this, 'plantDiamond', growthStage, potted, parentPlanet, config);
	var baseUpdateStage = self.updateStage;
	;
	self.action = action;
	self.updateStage = updateStage;
	self.strong = true;
	self.fruitOffsetMin = 123;
	self.fruitOffsetMax = 123;
	;
	function action() {
		if (self.actionable) {
			self.harvest();
			self.parent.removePlant();
			return true;
		}
		return false;
	}
	function updateStage(newStage) {
		baseUpdateStage(newStage);
		;
		if (newStage === self.growthStages) {
			self.fruitCount = 1;
			self.actionable = true;
		}
	}
	;
	self.updateStage(growthStage);
	return self;
});
var Fruit = ConfigContainer.expand(function (fruitName, parentPlanet, config) {
	var self = ConfigContainer.call(this, config);
	var falling = true;
	var fruitGraphics = self.attachAsset(fruitName, {
		anchorX: 0.5,
		anchorY: 0.5,
		scaleX: SCALE_FRUIT_DROP * (Math.random() < 0.5 ? 1 : -1),
		scaleY: SCALE_FRUIT_DROP
	});
	;
	self.fruitName = fruitName;
	self.update = update;
	;
	function update(idle) {
		// Check if fruit is within collection range
		if (player && !idle) {
			var dx = self.x - player.x;
			var dy = self.y - player.y;
			var sqrDistance = dx * dx + dy * dy;
			if (sqrDistance < PLAYER_ACTION_SQRDIST) {
				inventory.adjustItem(fruitName, 1);
				stats.cropsHarvested++;
				var popupEffect = popupMap[fruitName];
				if (!popupEffect) {
					var rotation = player.rotation;
					var offset = parentPlanet.radius + POPUP_OFFSET;
					popupEffect = new PopupEffect(fruitName, {
						x: Math.cos(rotation - MATH_HALF_PI) * offset,
						y: Math.sin(rotation - MATH_HALF_PI) * offset,
						rotation: rotation
					});
				} else {
					popupEffect.increment();
				}
				return true;
			}
		}
		// Handle fruit falling towards the planet
		if (falling) {
			var angle = self.rotation + MATH_HALF_PI;
			self.x += Math.cos(angle) * FRUIT_FALL_SPEED;
			self.y += Math.sin(angle) * FRUIT_FALL_SPEED;
			if (self.x * self.x + self.y * self.y <= parentPlanet.radius * parentPlanet.radius) {
				falling = false;
			}
		}
	}
	;
	parentPlanet.fruitList.push(parentPlanet.addChild(self));
	return self;
});
var PopupEffect = ConfigContainer.expand(function (fruitName, config) {
	var self = ConfigContainer.call(this, config);
	var count = 1;
	var lifetime = POPUP_DURATION;
	var symbolText = self.addChild(new SymbolText(count + '×', fruitName, {
		anchorX: 0.5,
		anchorY: 1,
		suffix: true,
		scale: SCALE_FRUIT_TRADE
	}));
	;
	self.update = update;
	self.increment = increment;
	;
	function update() {
		if (--lifetime > 0) {
			symbolText.y -= 1;
			symbolText.alpha = lifetime / POPUP_DURATION;
			if (lifetime === POPUP_RECYCLE) {
				popupMap[fruitName] = undefined;
			}
		} else {
			self.destroy();
			return true;
		}
	}
	;
	function increment() {
		count++;
		symbolText.setText(count + '×');
	}
	;
	effectsList.push(planet.addChild(self));
	popupMap[fruitName] = self;
	return self;
});
var PlantNode = ConfigContainer.expand(function (config) {
	var self = ConfigContainer.call(this, config);
	;
	self.update = update;
	self.addPlant = addPlant;
	self.removePlant = removePlant;
	self.tryAction = tryAction;
	self.plant;
	self.farm;
	self.prev;
	self.next;
	;
	function update() {
		if (self.plant) {
			self.plant.update();
		}
	}
	function addPlant(plantName, stage, overrun, potted) {
		if (!self.plant || self.plant.uproot(overrun)) {
			if (self.plant) {
				self.removePlant();
			}
			var plantBlueprint = PLANT_DETAILS[plantName].blueprint;
			self.plant = self.addChild(new plantBlueprint(stage, potted, self.parent, {
				y: potted ? PLOT_FARM_OFFSET : PLOT_NODE_OFFSET
			}));
		}
	}
	function removePlant() {
		self.plant.destroy();
		self.plant = undefined;
	}
	function tryAction() {
		return !!self.plant && self.plant.actionable && self.plant.action();
	}
	;
	return self;
});
var WeedsNode = PlantNode.expand(function (config) {
	var self = PlantNode.call(this, config);
	var baseUpdate = self.update;
	var baseAddPlant = self.addPlant;
	var baseTryAction = self.tryAction;
	;
	self.update = update;
	self.addPlant = addPlant;
	self.tryAction = tryAction;
	;
	function update() {
		baseUpdate();
		if (!self.plant && --spawnCountdown <= 0) {
			self.addPlant('plantWeeds', 1, false, false);
		}
	}
	function addPlant(plantName, stage, overrun, potted) {
		baseAddPlant(plantName, stage, overrun, potted);
		refreshCountdown();
	}
	function tryAction() {
		if (baseTryAction()) {
			refreshCountdown();
			return true;
		}
		return false;
	}
	function refreshCountdown() {
		var baseTime = WEEDS_SPAWN_TIME + Math.random() * WEEDS_SPAWN_VARIANCE;
		spawnCountdown = Math.floor(PLANT_GROWTH_FACTOR * baseTime);
	}
	;
	refreshCountdown();
});
var FarmNode = PlantNode.expand(function (config) {
	var self = PlantNode.call(this, config);
	var baseUpdate = self.update;
	var baseAddPlant = self.addPlant;
	var baseTryAction = self.tryAction;
	var targetAlpha = 0;
	var planterButton;
	var uprootButton;
	var currentAlpha = targetAlpha;
	var farmGraphics = self.attachAsset('farm', {
		anchorX: 0.5,
		anchorY: 0.6,
		alpha: currentAlpha
	});
	;
	self.update = update;
	self.addPlant = addPlant;
	self.tryAction = tryAction;
	self.tryPlantFruit = tryPlantFruit;
	;
	function update() {
		baseUpdate();
		// Control the in/out fade when overrun
		if (currentAlpha !== targetAlpha) {
			if (currentAlpha < targetAlpha) {
				currentAlpha = Math.min(targetAlpha, currentAlpha + PLOT_ALPHA_STEP);
			} else {
				currentAlpha = Math.max(targetAlpha, currentAlpha - PLOT_ALPHA_STEP);
			}
			farmGraphics.alpha = currentAlpha;
		}
		// Show/hide the planter button
		var fruitName = inventory.getSelection();
		if (planterRequirements(fruitName)) {
			if (!planterButton) {
				planterButton = self.addChild(new PlanterButton(fruitName, planterButtonCallback, {
					scale: SCALE_FRUIT_PLANTER
				}));
			} else {
				planterButton.updateImage(fruitName);
			}
		} else if (planterButton) {
			planterButton.destroy();
			planterButton = undefined;
		}
		// Show/hide the uproot button
		if (uprootRequirements()) {
			if (!uprootButton) {
				uprootButton = self.addChild(new UprootButton(fruitName, uprootButtonCallback, {}));
			}
		} else if (uprootButton) {
			uprootButton.destroy();
			uprootButton = undefined;
		}
	}
	function tryAction() {
		if (baseTryAction()) {
			targetAlpha = 1;
			return true;
		} else if (taskPlanter) {
			tryPlantFruit(taskPlanter);
			taskPlanter = false;
		} else if (taskUproot) {
			tryUproot();
			taskPlanter = true;
		}
	}
	function tryUproot() {
		if (uprootRequirements()) {
			if (self.plant.uproot(false)) {
				self.removePlant();
			}
		}
	}
	function tryPlantFruit(fruitName) {
		if (planterRequirements(fruitName)) {
			var plantName = FRUIT_DETAILS[fruitName].plant;
			self.addPlant(plantName, 1, false, true);
			inventory.adjustItem(fruitName, -1);
			stats.cropsPlanted++;
		}
	}
	function addPlant(plantName, stage, overrun, potted) {
		baseAddPlant(plantName, stage, overrun, potted);
		targetAlpha = potted ? 1 : 0;
	}
	function planterRequirements(fruitName) {
		return !self.plant && currentAlpha === 1 && inventory.allowed && inventory.getQuantity(fruitName) > 0;
	}
	function planterButtonCallback() {
		resetTasks();
		taskPlanter = inventory.getSelection();
		targetAngle = Math.atan2(self.y, self.x);
		skipRetarget = true;
	}
	function uprootRequirements() {
		return !!self.plant && self.plant.uprootable && self.plant.potted;
	}
	function uprootButtonCallback() {
		resetTasks();
		taskUproot = true;
		targetAngle = Math.atan2(self.y, self.x);
		skipRetarget = true;
	}
});
var Planet = ConfigContainer.expand(function (planetName, config) {
	var self = ConfigContainer.call(this, config);
	var details = PLANET_DETAILS[planetName];
	var perimeter = MATH_2_PI * details.radius;
	var numPlots = Math.floor(perimeter / (PLOT_SIZE + PLOT_GAP));
	var numNodes = numPlots * 4;
	var nodes = [];
	var fruitList = [];
	var background = self.addChild(new Container());
	var planetScale = 2 * details.radius / 1000;
	var planetGraphics = self.createAsset(planetName, {
		anchorX: 0.5,
		anchorY: 0.5,
		scaleX: planetScale,
		scaleY: planetScale
	});
	;
	self.update = update;
	self.name = planetName;
	self.barren = !!details.barren; // No plant life
	self.coarse = !!details.coarse; // No weed nodes (still starts + grows + spreads)
	self.nodes = nodes;
	self.background = background;
	self.fruitList = fruitList;
	self.spin = details.spin;
	self.radius = details.radius;
	;
	function update(ticks) {
		self.rotation += self.spin;
		updateList(nodes);
		updateList(fruitList, planet !== self);
	}
	function createNodes() {
		if (!self.barren) {
			var defaultPlant = 'plantWeeds';
			var maxPlantStage = PLANT_DETAILS[defaultPlant].stages;
			for (var i = 0; i < numNodes; i++) {
				var angle = i / numNodes * MATH_2_PI;
				var nodeX = self.radius * Math.cos(angle);
				var nodeY = self.radius * Math.sin(angle);
				var rotation = angle + MATH_HALF_PI;
				var farmNode = i !== 0 && i % 4 === 0;
				var nodeType = farmNode ? FarmNode : i % 2 === 0 && !self.coarse ? WeedsNode : PlantNode;
				var node = self.addChild(new nodeType({
					x: nodeX,
					y: nodeY,
					rotation: rotation
				}));
				if (farmNode) {
					node.addPlant(defaultPlant, maxPlantStage);
				} else if (Math.random() < WEEDS_SPAWN_CHANCE) {
					node.addPlant(defaultPlant, 1 + Math.floor(Math.random() * maxPlantStage));
				}
				if (i !== 0) {
					node.prev = nodes[i - 1];
					node.prev.next = node;
					if (i === numNodes - 1) {
						node.next = nodes[0];
						node.next.prev = node;
					}
				}
				nodes.push(node);
			}
		}
	}
	;
	createNodes();
	return self;
});
var PlanetGrey = Planet.expand(function (config) {
	var self = Planet.call(this, 'planetGrey', config);
});
var PlanetRed = Planet.expand(function (config) {
	var self = Planet.call(this, 'planetRed', config);
});
var PlanetBlue = Planet.expand(function (config) {
	var self = Planet.call(this, 'planetBlue', config);
});
var PlanetOmni = Planet.expand(function (config) {
	var self = Planet.call(this, 'planetOmni', config);
});
var PlanetGold = Planet.expand(function (config) {
	var self = Planet.call(this, 'planetGold', config);
});
var Crosshair = Container.expand(function () {
	var self = Container.call(this);
	var counter = 0;
	var arrows = [];
	var arrowOffsets = [{
		x: 0,
		y: -1
	}, {
		x: 1,
		y: 0
	}, {
		x: 0,
		y: 1
	}, {
		x: -1,
		y: 0
	}];
	for (var i = 0; i < arrowOffsets.length; i++) {
		var arrow = self.attachAsset('chevron', {
			anchorX: 0.5,
			anchorY: 1
		});
		arrow.rotation = MATH_HALF_PI * i;
		arrows.push(arrow);
		self.addChild(arrow);
	}
	;
	self.rotation = MATH_QUARTER_PI;
	self.update = update;
	;
	function update() {
		if (currentPlanet !== destinationPlanet) {
			counter++;
			var distance = CROSSHAIR_DIST + CROSSHAIR_VARIANCE * Math.sin(counter / CROSSHAIR_PERIOD);
			setDistance(distance);
		} else if (counter > 0) {
			counter = 0;
			setDistance(CROSSHAIR_DIST);
		}
	}
	function setDistance(distance) {
		for (var i = 0; i < arrows.length; i++) {
			arrows[i].x = arrowOffsets[i].x * distance;
			arrows[i].y = arrowOffsets[i].y * distance;
		}
	}
	;
	setDistance(CROSSHAIR_DIST);
});
var NavigationButton = ConfigContainer.expand(function (planetName, callback, config) {
	var self = ConfigContainer.call(this, config);
	var details = PLANET_DETAILS[planetName];
	var buttonGraphics = self.attachAsset('navigationButton', {
		anchorX: 0.5,
		anchorY: 0.5,
		tint: 0xFFAAAA
	});
	var planetGraphics = self.createAsset(planetName, {
		anchorX: 0.5,
		anchorY: 0.5,
		width: buttonGraphics.width * 0.4,
		height: buttonGraphics.width * 0.4
	});
	var currencyText = self.addChild(new SymbolText(details.cost, 'currencySymbol', {
		anchorX: .5,
		anchorY: .5,
		tint: true
	}));
	;
	self.unlock = unlock;
	self.cost = details.cost;
	self.planet = planetName;
	self.unlocked = !details.cost;
	self.on('down', function () {
		callback(planetName);
	});
	;
	function unlock() {
		self.unlocked = true;
		buttonGraphics.tint = 0xFFFFFF;
		currencyText.destroy();
	}
	;
	if (self.unlocked) {
		unlock();
	}
});
var NavigationInterface = ConfigContainer.expand(function (config) {
	var self = ConfigContainer.call(this, config);
	var buttons = {};
	var baseOffset = -NAVIGATION.length / 2 + 0.5;
	var firstPlanet = NAVIGATION[0];
	for (var i = 0; i < NAVIGATION.length; i++) {
		var planet = NAVIGATION[i];
		var buttonX = (baseOffset + i) * 200;
		buttons[planet] = self.addChild(new NavigationButton(planet, buttonCallback, {
			x: buttonX,
			y: 0
		}));
	}
	;
	self.setDestination = setDestination;
	;
	function buttonCallback(planet) {
		var button = buttons[planet];
		if (button.unlocked) {
			setDestination(planet);
		} else if (money >= button.cost) {
			moneyDisplay.setText(money -= button.cost);
			button.unlock();
		}
	}
	function setDestination(destPlanet) {
		if (player) {
			destinationPlanet = destPlanet;
			buttons[destPlanet].addChild(crosshair);
			resetTasks();
			if (destinationPlanet !== currentPlanet) {
				taskLaunch = true;
				targetAngle = 0;
			}
		}
	}
	;
	buttonCallback(firstPlanet);
	buttons[firstPlanet].addChild(crosshair);
});
var WinningMessage = Container.expand(function (x, y, completionTicks) {
	var self = Container.call(this);
	var seconds = Math.floor(completionTicks / 60);
	var minutes = Math.floor(seconds / 60);
	var hours = Math.floor(minutes / 60);
	seconds = seconds % 60;
	minutes = minutes % 60;
	var winningTime = (hours > 0 ? hours + 'h ' : '') + (minutes > 0 ? minutes + 'm ' : '') + seconds + 's';
	var messageStatTitles = ['Distance Walked', 'Credits Earned', 'Items Collected', 'Crops Planted', 'Crops Overrun', 'Weeds Pulled', 'Trades Accepted', 'Trades Declined', 'Rocket Launches', 'Asteroid Impacts'];
	var messageStatKeys = ['distanceRun', 'creditsEarned', 'cropsHarvested', 'cropsPlanted', 'cropsOverrun', 'weedsPulled', 'salesDone', 'salesRejected', 'rocketLaunches', 'asteroidImpacts'];
	var timeText = self.addChild(new BorderedText('You reached the Gold Planet in ' + winningTime, {
		anchorX: .5,
		anchorY: 1,
		y: -TEXT_WINNING_OFFSET
	}));
	self.addChild(new BorderedText('Congratulations, you won!', {
		anchorX: .5,
		anchorY: 1,
		size: TEXT_SIZE_LARGE,
		y: timeText.y - timeText.height - 10
	}));
	self.addChild(new BorderedText(messageStatTitles.map(mapTitle).join('\n'), {
		anchorX: 1,
		anchorY: 0,
		size: TEXT_SIZE_SMALL,
		y: TEXT_WINNING_OFFSET
	}));
	self.addChild(new BorderedText(messageStatKeys.map(mapKey).join('\n'), {
		anchorX: 0,
		anchorY: 0,
		size: TEXT_SIZE_SMALL,
		y: TEXT_WINNING_OFFSET
	}));
	;
	self.x = x;
	self.y = y;
	;
	function mapTitle(title) {
		return title + ' ';
	}
	function mapKey(key) {
		var stat = winningStats[key];
		var extra = stats[key] - stat;
		switch (key) {
			case 'distanceRun':
				return ': ' + Math.round(stat * PLAYER_DISTANCE_SCALE) + 'm' + (extra ? ' [+' + Math.round(extra * PLAYER_DISTANCE_SCALE) + 'm]' : '');
			default:
				return ': ' + stat + (extra ? ' [+' + extra + ']' : '');
		}
	}
});
var InventorySlot = ConfigContainer.expand(function (index, config) {
	var self = ConfigContainer.call(this, config);
	var itemDisplay;
	var frame = self.attachAsset('inventoryFrame', {
		anchorX: .5,
		anchorY: .5
	});
	var quantityText = self.addChild(new BorderedText('', {
		size: 30,
		anchorX: .5,
		anchorY: .5,
		y: INVENTORY_SLOT_SIZE / 2.2
	}));
	frame.width = INVENTORY_SLOT_SIZE;
	frame.height = INVENTORY_SLOT_SIZE;
	;
	self.setItem = setItem;
	self.adjustQuantity = adjustQuantity;
	self.index = index;
	self.item = null;
	self.quantity = 0;
	;
	function adjustQuantity(amount) {
		self.quantity += amount;
		quantityText.setText(self.quantity);
	}
	function setItem(itemName) {
		self.item = itemName;
		if (itemDisplay) {
			itemDisplay.destroy();
		}
		itemDisplay = self.attachAsset(itemName, {
			anchorX: 0.5,
			anchorY: 0.5,
			scaleX: SCALE_FRUIT_INVENTORY,
			scaleY: SCALE_FRUIT_INVENTORY
		});
	}
	;
	self.on('down', function () {
		self.parent.selectSlot(self.index);
	});
});
var Inventory = ConfigContainer.expand(function (config) {
	var self = ConfigContainer.call(this, config);
	var slots = [];
	var itemIndices = {};
	var selectedIndex = 0;
	var widthOffset = INVENTORY_SLOT_SIZE * INVENTORY_COLS * (config.anchorX || 0);
	var heightOffset = INVENTORY_SLOT_SIZE * INVENTORY_ROWS * (config.anchorY || 0);
	for (var row = 0; row < INVENTORY_ROWS; row++) {
		for (var col = 0; col < INVENTORY_COLS; col++) {
			var index = row * INVENTORY_COLS + col;
			slots.push(self.addChild(new InventorySlot(index, {
				x: (col + .5) * INVENTORY_SLOT_SIZE - widthOffset,
				y: (row + .5) * INVENTORY_SLOT_SIZE - heightOffset
			})));
		}
	}
	var selector = new InventorySelector();
	var warningText = self.addChild(new BorderedText('', {
		y: INVENTORY_SLOT_SIZE * INVENTORY_ROWS / 2 + 20,
		anchorX: 0.5,
		anchorY: 0,
		size: TEXT_SIZE_SMALL
	}));
	warningText.visible = false;
	;
	self.adjustItem = adjustItem;
	self.selectSlot = selectSlot;
	self.getQuantity = getQuantity;
	self.getSelection = getSelection;
	self.refreshAllowed = refreshAllowed;
	self.selector = selector;
	self.allowed = false;
	;
	function selectSlot(index) {
		slots[index].addChild(selector);
		selectedIndex = index;
		refreshAllowed();
		resetTasks();
	}
	function adjustItem(item, quantity) {
		var slotIndex = itemIndices[item];
		var newItem = false;
		if (slotIndex === undefined) {
			for (slotIndex = 0; slotIndex < slots.length; slotIndex++) {
				if (!slots[slotIndex].item) {
					break;
				}
			}
			slots[slotIndex].setItem(item);
			newItem = true;
		}
		itemIndices[item] = slotIndex;
		slots[slotIndex].adjustQuantity(quantity);
		if (newItem && slotIndex === selectedIndex) {
			slots[slotIndex].addChild(selector);
			refreshAllowed();
		}
	}
	function getSelection() {
		return slots[selectedIndex].item;
	}
	function getQuantity(item) {
		var slotIndex = itemIndices[item];
		return slotIndex === undefined ? 0 : slots[slotIndex].quantity;
	}
	function refreshAllowed() {
		var selection = getSelection();
		var allowed = true;
		if (selection) {
			var itemDetails = FRUIT_DETAILS[selection];
			var allowedTypes = PLANET_DETAILS[planet.name].plantTypes;
			allowed = allowedTypes.includes(itemDetails.type);
			if (!allowed) {
				var warning = planet.barren ? GROW_WARNING_BARREN : !itemDetails.type || !itemDetails.plant ? GROW_WARNING_DIAMOND : GROW_WARNING_GENERAL;
				warningText.setText(warning);
			}
		}
		self.allowed = allowed;
		selector.toggleAllowed(allowed);
		warningText.visible = !allowed;
	}
	;
	selectSlot(0);
	adjustItem('fruitWeeds', 0);
});
var InventorySelector = Container.expand(function () {
	var self = Container.call(this);
	var selector = self.attachAsset('inventorySelector', {
		anchorX: 0.5,
		anchorY: 0.5,
		tint: 0xFF3333
	});
	var crossAsset = self.attachAsset('cross', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	;
	self.toggleAllowed = toggleAllowed;
	;
	function toggleAllowed(bool) {
		crossAsset.alpha = bool ? 0 : 1;
	}
});
var BasicButton = ConfigContainer.expand(function (imageName, callback, config) {
	var self = ConfigContainer.call(this, config);
	config = config || {};
	var anchorX = config.anchorX !== undefined ? config.anchorX : 0.5;
	var anchorY = config.anchorY !== undefined ? config.anchorY : 0.5;
	var scale = config.scale !== undefined ? config.scale : 1;
	var scaleX = config.scaleX !== undefined ? config.scaleX : scale;
	var scaleY = config.scaleY !== undefined ? config.scaleY : scale;
	var buttonBackground = self.attachAsset('buttonBackground', {
		anchorX: anchorX,
		anchorY: anchorY
	});
	var imageAsset;
	if (imageName) {
		imageAsset = self.attachAsset(imageName, {
			x: buttonBackground.x - buttonBackground.width * (anchorX - 0.5),
			y: buttonBackground.y - buttonBackground.height * (anchorY - 0.5),
			anchorX: 0.5,
			anchorY: 0.5
		});
	}
	;
	self.updateImage = updateImage;
	self.imageName = imageName;
	self.imageAsset = imageAsset;
	self.buttonBackground = buttonBackground;
	;
	function updateImage(newImageName) {
		if (newImageName !== self.imageName) {
			self.imageName = newImageName;
			if (imageAsset) {
				imageAsset.destroy();
			}
			if (newImageName) {
				imageAsset = self.attachAsset(newImageName, {
					x: buttonBackground.x - buttonBackground.width * (anchorX - 0.5),
					y: buttonBackground.y - buttonBackground.height * (anchorY - 0.5),
					anchorX: 0.5,
					anchorY: 0.5
				});
			}
		}
	}
	;
	buttonBackground.on('down', callback);
	return self;
});
var PlanterButton = BasicButton.expand(function (plantName, callback, config) {
	var self = BasicButton.call(this, plantName, callback, config);
	var arrow = self.attachAsset('arrow', {
		y: PLOT_BUTTON_OFFSET / 2,
		anchorX: 0.5,
		anchorY: 0.5,
		scaleX: 1.4,
		scaleY: 1.4
	});
	;
	self.buttonBackground.y = PLOT_BUTTON_OFFSET;
	self.imageAsset.y = PLOT_BUTTON_OFFSET;
	;
	return self;
});
var UprootButton = BasicButton.expand(function (plantName, callback, config) {
	var self = BasicButton.call(this, 'cross', callback, config);
	;
	self.buttonBackground.y = PLOT_BUTTON_OFFSET;
	self.imageAsset.y = PLOT_BUTTON_OFFSET;
	;
	return self;
});
var Background = Container.expand(function () {
	var self = Container.call(this);
	var backgroundGraphics = self.attachAsset('background', {
		x: GAME_WIDTH / 2,
		y: GAME_HEIGHT / 2,
		anchorX: 0.5,
		anchorY: 0.5,
		alpha: 0.5
	});
	backgroundGraphics.width = GAME_WIDTH * 1.05;
	backgroundGraphics.height = GAME_WIDTH * 1.05;
	;
	self.refresh = refresh;
	;
	function refresh() {
		backgroundGraphics.rotation += Math.PI / 2;
	}
});
var Asteroid = ConfigContainer.expand(function (config) {
	var self = ConfigContainer.call(this, config);
	var randomScale = ASTEROID_SCALE_MIN + Math.random() * ASTEROID_SCALE_VAR;
	var asteroidGraphics = self.attachAsset('asteroid', {
		anchorX: 0.5,
		anchorY: 0.5,
		scaleX: randomScale,
		scaleY: randomScale,
		rotation: Math.random() * MATH_2_PI
	});
	var side = self.x < 0;
	var targetY = ASTEROID_TARGET_OFFSET + Math.random() * (GAME_HEIGHT - 2 * ASTEROID_TARGET_OFFSET);
	var targetX = side ? GAME_WIDTH + ASTEROID_MARGIN : -ASTEROID_MARGIN;
	var rotationSpeed = (Math.random() - 0.5) * 2 * ASTEROID_ROTATION_MAX;
	var angle = Math.atan2(targetY - self.y, targetX - self.x);
	var velocityX = ASTEROID_SPEED * Math.cos(angle);
	var velocityY = ASTEROID_SPEED * Math.sin(angle);
	;
	self.update = update;
	;
	function update() {
		self.rotation += rotationSpeed;
		self.x += velocityX;
		self.y += velocityY;
		var dx = self.x - planet.x;
		var dy = self.y - planet.y;
		var distanceSquared = dx * dx + dy * dy;
		if (distanceSquared <= planet.radius * planet.radius) {
			if (Math.random() < ASTEROID_DROP_CHANCE) {
				var angle = Math.atan2(self.y - planet.y, self.x - planet.x) - planet.rotation;
				var fruitX = Math.cos(angle) * planet.radius;
				var fruitY = Math.sin(angle) * planet.radius;
				var fruit = planet.addChild(new Fruit('fruitDiamondDust', planet, {
					rotation: angle + MATH_HALF_PI,
					x: fruitX,
					y: fruitY
				}));
			}
			var explosion = game.addChild(new Explosion({
				x: self.x,
				y: self.y,
				scale: randomScale
			}));
			stats.asteroidImpacts++;
			return true;
		}
		return side ? self.x > targetX : self.x < targetX;
	}
});
var Explosion = ConfigContainer.expand(function (config) {
	var self = ConfigContainer.call(this, config);
	var baseScale = config.scale;
	var frameCount = 0;
	var explosionGraphics = self.attachAsset('explosion', {
		anchorX: 0.25,
		anchorY: 0.5,
		scaleX: baseScale,
		scaleY: baseScale,
		alpha: 0.5,
		rotation: Math.atan2(self.y - planet.y, self.x - planet.x)
	});
	;
	self.update = update;
	;
	function update() {
		frameCount++;
		explosionGraphics.alpha -= 0.5 / EXPLOSION_FRAMES;
		explosionGraphics.scale.set(baseScale * (1 + frameCount / EXPLOSION_FRAMES));
		if (frameCount >= EXPLOSION_FRAMES) {
			self.destroy();
			return true;
		}
	}
	;
	effectsList.push(self);
});
var Trader = ConfigContainer.expand(function (config, setTrade) {
	var self = ConfigContainer.call(this, config);
	var index = config.index;
	var angle = Math.random() * MATH_2_PI;
	var distance = Math.random() * TRADER_SPAWN_RADIUS;
	var direction = 1;
	var spawnTick = LK.ticks;
	var counter = TRADER_SPAWN_TIME;
	var traderGraphics = self.attachAsset('trader', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: Math.cos(angle) * distance,
		y: Math.sin(angle) * distance,
		scaleX: 0,
		scaleY: 0
	});
	var moveX = -traderGraphics.x / TRADER_SPAWN_TIME;
	var moveY = -traderGraphics.y / TRADER_SPAWN_TIME;
	var trade = setTrade || generateTrade();
	var traderDialogue;
	;
	self.update = update;
	self.handleTradeCallback = handleTradeCallback;
	;
	function update() {
		if ((LK.ticks - spawnTick) % TRADER_MOVE_INTERVAL === 0) {
			var anchorX = 0.5 + TRADER_MOVE_ANCHOR * (Math.random() * 2 - 1);
			var anchorY = 0.5 + TRADER_MOVE_ANCHOR * (Math.random() * 2 - 1);
			var rotation = TRADER_MOVE_ROTATION * (Math.random() * 2 - 1);
			traderGraphics.anchor.set(anchorX, anchorY);
			traderGraphics.rotation = rotation;
		}
		if (direction !== 0) {
			counter -= direction;
			traderGraphics.x += moveX * direction;
			traderGraphics.y += moveY * direction;
			traderGraphics.scale.set(1 - counter / TRADER_SPAWN_TIME);
			if (counter <= 0) {
				direction = 0;
				traderDialogue = foreground.addChild(new TraderDialogue(handleTradeCallback, trade, {
					x: self.x,
					y: self.y - traderGraphics.height / 2 - TRADER_SHIP_SPACING,
					flipX: self.x < GAME_WIDTH / 2 ? -1 : 1
				}));
			} else if (counter >= TRADER_SPAWN_TIME) {
				traders[index] = undefined;
				self.destroy();
			}
		}
	}
	function handleTradeCallback(accepted) {
		var closeDialogue = false;
		skipRetarget = true;
		if (accepted) {
			if (checkValue(trade.sellName, trade.sellAmount)) {
				makeAdjustment(trade.sellName, -trade.sellAmount, false);
				makeAdjustment(trade.buyName, trade.buyAmount, true);
				stats.salesDone++;
				closeDialogue = true;
			} else {
				traderDialogue.setTint(0xFF0000);
			}
		} else {
			stats.salesRejected++;
			closeDialogue = true;
		}
		if (closeDialogue) {
			direction = -1;
			if (traderDialogue) {
				traderDialogue.destroy();
			}
		}
	}
	function generateTrade() {
		// Determine what the trade items are available
		var details = PLANET_DETAILS[planet.name];
		var buyList = details.buy;
		var buyName = buyList[randomInt(0, buyList.length)];
		var sellList = details.sell.filter(function (item) {
			return item !== buyName;
		});
		var sellName = sellList[randomInt(0, sellList.length)];
		// Determine trade value and quantities
		var sellAmount, buyAmount;
		var valueFactor = 1 + TRADER_COST_VARIANCE * (Math.random() * 2 - 1);
		if (sellName === 'credits') {
			// Player buying Fruit with Credits
			var value = valueFactor * FRUIT_DETAILS[buyName].value;
			buyAmount = randomInt(TRADER_FRUIT_OFFER_MIN, TRADER_FRUIT_OFFER_MAX);
			sellAmount = Math.round(buyAmount * value);
		} else if (buyName === 'credits') {
			// Player selling Fruit for Credits
			var fruitDetails = FRUIT_DETAILS[sellName];
			var minSell = fruitDetails.minSell || TRADER_MINIMUM_BUY;
			var value = valueFactor * FRUIT_DETAILS[sellName].value;
			var quantity = Math.max(1, inventory.getQuantity(sellName));
			sellAmount = Math.max(minSell, randomInt(quantity * TRADER_INVENTORY_MIN, quantity * TRADER_INVENTORY_MAX));
			buyAmount = Math.round(sellAmount * value);
		} else {
			// Fruit exchange
			var multiplier = 1 + Math.random() * (TRADER_EXCHANGE_MULTIPLIER - 1);
			var sellValue = valueFactor * FRUIT_DETAILS[buyName].value;
			var buyValue = FRUIT_DETAILS[sellName].value;
			var lowValue = Math.min(sellValue, buyValue);
			sellAmount = Math.ceil(multiplier * sellValue / lowValue);
			buyAmount = Math.ceil(multiplier * buyValue / lowValue);
		}
		return {
			sellName: sellName,
			sellAmount: sellAmount,
			buyName: buyName,
			buyAmount: buyAmount
		};
	}
	function checkValue(name, amount) {
		if (name === 'credits') {
			return money >= amount;
		} else {
			return inventory.getQuantity(name) >= amount;
		}
	}
	function makeAdjustment(name, amount, collectStats) {
		if (name === 'credits') {
			moneyDisplay.setText(money += amount);
			if (collectStats) {
				stats.creditsEarned += amount;
			}
		} else {
			inventory.adjustItem(name, amount);
			if (collectStats) {
				stats.itemsCollected += amount;
			}
		}
	}
	;
	return self;
});
var TraderDialogue = ConfigContainer.expand(function (callback, trade, config) {
	var self = ConfigContainer.call(this, config);
	config = config || {};
	var flipX = config.flipX;
	var dialogueBackground = self.attachAsset('traderDialogue', {
		anchorX: TRADER_FRAME_OFFSET_X,
		anchorY: 1,
		scaleX: flipX
	});
	var centerFrameX = dialogueBackground.width * (0.5 - TRADER_FRAME_OFFSET_X) * flipX;
	var centerFrameY = dialogueBackground.height * TRADER_FRAME_OFFSET_Y;
	var tradeSymbols = self.addChild(new Container());
	var tradeArrow = tradeSymbols.attachAsset('arrow', {
		anchorX: 0.5,
		anchorY: 0.5,
		scaleX: 1.4,
		scaleY: 1.4,
		rotation: -MATH_HALF_PI
	});
	var tradeSell = createTradePart(trade.sellName, trade.sellAmount, {
		x: -tradeArrow.height * 0.7 - TRADER_DETAIL_MARGIN,
		anchorX: 1
	});
	var tradeBuy = createTradePart(trade.buyName, trade.buyAmount, {
		x: tradeArrow.height * 0.7 + TRADER_DETAIL_MARGIN,
		anchorX: 0
	});
	self.addChild(new TraderButtons(callback, {
		x: -(dialogueBackground.width * TRADER_FRAME_OFFSET_X + TRADER_DETAIL_MARGIN) * flipX,
		y: centerFrameY,
		anchorX: 0.5 + 0.5 * flipX
	}));
	tradeSymbols.x = centerFrameX;
	tradeSymbols.y = centerFrameY;
	;
	self.setTint = setTint;
	;
	function setTint(tint) {
		LK.effects.flashObject(dialogueBackground, tint, 500);
	}
	function createTradePart(name, amount, config) {
		if (name === 'credits') {
			return tradeSymbols.addChild(new SymbolText(amount, 'currencySymbol', {
				x: config.x,
				anchorX: config.anchorX,
				anchorY: 0.5,
				tint: true
			}));
		} else {
			return tradeSymbols.addChild(new SymbolText(amount + '×', name, {
				x: config.x,
				anchorX: config.anchorX,
				anchorY: 0.5,
				suffix: true,
				scale: SCALE_FRUIT_TRADE
			}));
		}
	}
	;
	return self;
});
var TraderButtons = ConfigContainer.expand(function (callback, config) {
	var self = ConfigContainer.call(this, config);
	var checkButton = self.addChild(new BasicButton('check', function () {
		callback(true);
	}, {
		y: -TRADER_DETAIL_MARGIN / 2,
		anchorX: config.anchorX,
		anchorY: 1
	}));
	var crossButton = self.addChild(new BasicButton('cross', function () {
		callback(false);
	}, {
		y: TRADER_DETAIL_MARGIN / 2,
		anchorX: config.anchorX,
		anchorY: 0
	}));
	;
	return self;
});
/**** 
* Initialize Game
****/
var game = new LK.Game({
	backgroundColor: 0x000000 // Init game with black background
});
/**** 
* Game Code
****/
;
//==============================================================================
// Global constants & settings
//==============================================================================
;
// Math constants / pre-calculations
var MATH_2_PI = Math.PI * 2;
var MATH_HALF_PI = Math.PI / 2;
var MATH_QUARTER_PI = Math.PI / 4;
var MATH_HALF_ROOT_3 = Math.sqrt(3) / 2; // Required by: TEXT_OFFSETS, BorderedText, BorderedSymbol, SymbolText
var MATH_APPROX_ZERO = 0.0000001;
;
// Text settings
var TEXT_OFFSETS = [[0, 1], [MATH_HALF_ROOT_3, 0.5], [MATH_HALF_ROOT_3, -0.5], [0, -1], [-MATH_HALF_ROOT_3, -0.5], [-MATH_HALF_ROOT_3, 0.5], [0, 0]]; // Required by: BorderedText, BorderedSymbol, SymbolText
var TEXT_BORDER_WEIGHT = 4; // Required by: BorderedText, BorderedSymbol, SymbolText
var TEXT_DEFAULT_BORDER = '#000000'; // Required by: BorderedText, BorderedSymbol, SymbolText
var TEXT_DEFAULT_FILL = '#FFFFFF'; // Required by: BorderedText, SymbolText
var TEXT_DEFAULT_FONT = 'Arial'; // Required by: BorderedText, SymbolText
var TEXT_DEFAULT_SIZE = 50; // Required by: BorderedText, SymbolText
var TEXT_DEFAULT_MARGIN = 0; // Required by: SymbolText
;
// Game constants
var GAME_TESTING = false;
var GAME_TICKS = 60;
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var GAME_SPEED = 0.1;
;
// Rocket constants
var ROCKET_DIST_REVERSE = 300;
var ROCKET_DIST_LEAVE = 2500;
var ROCKET_SPEED_BASE = 1.2;
var ROCKET_SPEED_DIV = 5;
var ROCKET_SPEED_DELAY = 10;
var ROCKET_SPEED_REVERSE = 2;
var ROCKET_FLAME_BREDTH = 30;
var ROCKET_FLAME_LIFETIME = 30;
var ROCKET_FLAME_SPEED = 5;
;
// Farm constants
var PLOT_SIZE = 80;
var PLOT_GAP = 120;
var PLOT_NODE_OFFSET = 20;
var PLOT_FARM_OFFSET = -30;
var PLOT_ALPHA_STEP = 1 / GAME_TICKS;
var PLOT_BUTTON_OFFSET = -220;
;
// Interface settings
var SCALE_FRUIT_DROP = 0.8;
var SCALE_FRUIT_INVENTORY = 0.6;
var SCALE_FRUIT_TRADE = 0.65;
var SCALE_FRUIT_PLANTER = 0.25;
var TEXT_WINNING_OFFSET = 300;
var TEXT_SIZE_SMALL = 35;
var TEXT_SIZE_LARGE = 75;
var CROSSHAIR_DIST = 40;
var CROSSHAIR_VARIANCE = 10;
var CROSSHAIR_PERIOD = 1.25 * GAME_TICKS / MATH_2_PI;
var POPUP_OFFSET = 100;
var POPUP_DURATION = 1.5 * GAME_TICKS;
var POPUP_RECYCLE = Math.floor(POPUP_DURATION * 0.5);
;
// Inventory settings
var INVENTORY_ROWS = 1;
var INVENTORY_COLS = 7;
var INVENTORY_ICON_SCALE = 0.55;
var INVENTORY_SLOT_SIZE = 115;
;
// Asteroid settings
var ASTEROID_SPAWN_CHANCE = 0.001;
var ASTEROID_DROP_CHANCE = 0.1; // 10% chance
var ASTEROID_ROTATION_MAX = 0.02;
var ASTEROID_SPEED = 5;
var ASTEROID_MARGIN = 100;
var ASTEROID_SCALE_MIN = 0.75;
var ASTEROID_SCALE_VAR = 0.5;
var ASTEROID_TARGET_OFFSET = 500;
var EXPLOSION_FRAMES = 15; // Number of frames for the explosion animation to last
;
// Player settings
var PLAYER_SPEED = 5;
var PLAYER_SPAWN_DELAY = 500;
var PLAYER_ACTION_INTERVAL = Math.floor(GAME_TICKS / 4);
var PLAYER_ACTION_DIST = 80;
var PLAYER_ACTION_SQRDIST = PLAYER_ACTION_DIST * PLAYER_ACTION_DIST;
var PLAYER_BUFFER_DIST = 400;
var PLAYER_BUFFER_SQRDIST = PLAYER_BUFFER_DIST * PLAYER_BUFFER_DIST;
var PLAYER_START_ANGLE = Math.PI / 16;
var PLAYER_DISTANCE_SCALE = 0.01;
;
// Trader settings
var TRADER_TIME_INITIAL = 10 * GAME_TICKS;
var TRADER_TIME_MIN = 1 * GAME_TICKS;
var TRADER_TIME_MAX = 30 * GAME_TICKS;
var TRADER_COST_VARIANCE = 0.1;
var TRADER_INVENTORY_MIN = 0.25;
var TRADER_INVENTORY_MAX = 1.5;
var TRADER_MINIMUM_BUY = 5;
var TRADER_FRUIT_OFFER_MIN = 1;
var TRADER_FRUIT_OFFER_MAX = 3;
var TRADER_EXCHANGE_MULTIPLIER = 5;
var TRADER_FRAME_OFFSET_X = 0.1;
var TRADER_FRAME_OFFSET_Y = -0.56;
var TRADER_DETAIL_MARGIN = 20;
var TRADER_SHIP_SPACING = 50;
var TRADER_MOVE_INTERVAL = 20;
var TRADER_MOVE_ROTATION = 0.1;
var TRADER_MOVE_ANCHOR = 0.1;
var TRADER_SPAWN_TIME = 2 * GAME_TICKS;
var TRADER_SPAWN_RADIUS = 1000;
;
// Plant settings
var WEEDS_SPAWN_CHANCE = 0.6;
var WEEDS_SPAWN_TIME = 5 * 60 * GAME_TICKS;
var WEEDS_SPAWN_VARIANCE = 2 * 60 * GAME_TICKS;
var WEEDS_SPREAD_TIME = 1 * 60 * GAME_TICKS;
var WEEDS_SPREAD_VARIANCE = 20 * GAME_TICKS;
var GROW_WARNING_BARREN = 'Nothing grows on this planet';
var GROW_WARNING_GENERAL = 'This cannot grow on this planet';
var GROW_WARNING_DIAMOND = 'This cannot be planted, only sold';
var FRUIT_FALL_SPEED = 3;
var PLANT_SCALE_VARIANCE = 0.1;
var PLANT_GROWTH_FACTOR = 0.2;
var PLANT_DEAD_COLOUR = 0xA3784B;
var PLANT_DETAILS = {
	plantWeeds: {
		blueprint: PlantWeeds,
		stages: 4,
		fruit: 'fruitWeeds',
		growthTime: 60 * GAME_TICKS,
		growthVariance: 10 * GAME_TICKS
	},
	plantBush: {
		blueprint: PlantBush,
		stages: 6,
		fruit: 'fruitBush',
		growthTime: 40 * GAME_TICKS,
		growthVariance: 10 * GAME_TICKS
	},
	plantStalk: {
		blueprint: PlantStalk,
		stages: 7,
		fruit: 'fruitStalk',
		growthTime: 50 * GAME_TICKS,
		growthVariance: 15 * GAME_TICKS
	},
	plantEyeball: {
		blueprint: PlantEyeball,
		stages: 5,
		fruit: 'fruitEyeball',
		growthTime: 35 * GAME_TICKS,
		growthVariance: 20 * GAME_TICKS
	},
	plantFlower: {
		blueprint: PlantFlower,
		stages: 7,
		fruit: 'fruitFlower',
		growthTime: 60 * GAME_TICKS,
		growthVariance: 30 * GAME_TICKS
	},
	plantDiamond: {
		blueprint: PlantDiamond,
		stages: 4,
		fruit: 'fruitDiamond',
		growthTime: 100 * GAME_TICKS,
		growthVariance: 30 * GAME_TICKS
	}
};
var FRUIT_DETAILS = {
	fruitWeeds: {
		value: 1,
		type: 'green',
		plant: 'plantWeeds'
	},
	fruitBush: {
		value: 5,
		type: 'green',
		plant: 'plantBush'
	},
	fruitStalk: {
		value: 5,
		type: 'red',
		plant: 'plantStalk'
	},
	fruitEyeball: {
		value: 10,
		type: 'red',
		plant: 'plantEyeball'
	},
	fruitFlower: {
		value: 15,
		type: 'blue',
		plant: 'plantFlower'
	},
	fruitDiamondDust: {
		value: 0,
		type: 'omni',
		plant: 'plantDiamond'
	},
	fruitDiamond: {
		value: 250,
		minSell: 1
	}
};
;
// Planet & navigation settings
var NAVIGATION = ['planetGrey', 'planetRed', 'planetBlue', 'planetOmni', 'planetGold'];
var PLANET_LAST = NAVIGATION[NAVIGATION.length - 1];
var PLANET_DETAILS = {
	planetGrey: {
		cost: 0,
		radius: 250,
		spin: 0.001,
		blueprint: PlanetGrey,
		plantTypes: ['green'],
		buy: ['credits', 'credits', 'fruitBush', 'fruitStalk'],
		sell: ['credits', 'fruitWeeds', 'fruitBush']
	},
	planetRed: {
		cost: 75,
		radius: 300,
		spin: -0.0002,
		blueprint: PlanetRed,
		plantTypes: ['green', 'red'],
		buy: ['credits', 'credits', 'fruitStalk', 'fruitEyeball'],
		sell: ['credits', 'fruitWeeds', 'fruitBush', 'fruitStalk', 'fruitEyeball']
	},
	planetBlue: {
		cost: 250,
		radius: 250,
		spin: 0.001,
		blueprint: PlanetBlue,
		plantTypes: ['green', 'blue'],
		buy: ['credits', 'credits', 'fruitFlower'],
		sell: ['credits', 'fruitWeeds', 'fruitBush', 'fruitFlower']
	},
	planetOmni: {
		cost: 1000,
		radius: 350,
		spin: -0.0005,
		coarse: true,
		blueprint: PlanetOmni,
		plantTypes: ['green', 'blue', 'red', 'omni'],
		buy: ['credits'],
		sell: ['fruitStalk', 'fruitEyeball', 'fruitFlower', 'fruitDiamond', 'fruitDiamond']
	},
	planetGold: {
		cost: 3250,
		radius: 150,
		spin: -0.002,
		barren: true,
		blueprint: PlanetGold,
		plantTypes: [],
		buy: [],
		sell: []
	}
};
;
//==============================================================================
// Global variables
//==============================================================================
;
var money = 25;
var planetMap = {};
var asteroidList = [];
var effectsList = [];
var popupMap = {};
var stats = {
	creditsEarned: 0,
	cropsPlanted: 0,
	cropsHarvested: 0,
	cropsOverrun: 0,
	weedsPulled: 0,
	salesDone: 0,
	salesRejected: 0,
	distanceRun: 0,
	rocketLaunches: 0,
	asteroidImpacts: 0
};
var winningStats = {};
var winningTick = -1;
var winningTime = '';
var winningMessage;
var player;
var traders = [];
var traderCountdown = TRADER_TIME_INITIAL;
var nextTrade = {
	buyName: 'fruitBush',
	buyAmount: 2,
	sellName: 'fruitWeeds',
	sellAmount: 9 + Math.round(2 * Math.random())
};
var skipRetarget = false;
var retargetAngle;
var targetAngle = PLAYER_START_ANGLE;
var taskLaunch = false;
var taskPlanter = false;
var taskUproot = false;
var currentPlanet = NAVIGATION[0];
var destinationPlanet = NAVIGATION[0];
var background = game.addChild(new Background());
var midground = game.addChild(new Container());
var foreground = game.addChild(new Container());
var planet = midground.addChild(new PlanetGrey({
	x: GAME_WIDTH / 2,
	y: GAME_HEIGHT / 2,
	rotation: Math.random() * MATH_2_PI
}));
var ship = planet.background.addChild(new Ship(planet.radius + ROCKET_DIST_REVERSE, 0));
var inventory = LK.gui.top.addChild(new Inventory({
	y: INVENTORY_SLOT_SIZE / 2 + 10,
	anchorX: .5,
	anchorY: .5
}));
var moneyDisplay = LK.gui.top.addChild(new SymbolText(money, 'currencySymbol', {
	x: inventory.width / 2 + 25,
	y: inventory.y,
	tint: true,
	anchorX: 0,
	anchorY: .5
}));
var crosshair = new Crosshair();
var navigation = LK.gui.bottom.addChild(new NavigationInterface({
	x: 0,
	y: -100
}));
planetMap[planet.name] = planet;
// Adjustments made purely for testing purposes
if (GAME_TESTING) {
	money = 50000;
	PLANT_GROWTH_FACTOR = 0.1;
	inventory.adjustItem('fruitWeeds', 10);
	inventory.adjustItem('fruitBush', 10);
	inventory.adjustItem('fruitStalk', 10);
	inventory.adjustItem('fruitEyeball', 10);
	inventory.adjustItem('fruitFlower', 10);
	inventory.adjustItem('fruitDiamondDust', 10);
}
;
//==============================================================================
// Gameplay events
//==============================================================================
;
LK.on('tick', function () {
	if (skipRetarget) {
		skipRetarget = false;
	} else if (retargetAngle !== undefined) {
		targetAngle = retargetAngle;
	}
	retargetAngle = undefined;
	if (player) {
		player.update();
	}
	ship.update();
	crosshair.update();
	updateList(effectsList);
	for (var key in planetMap) {
		planetMap[key].update();
	}
	for (var i = 0; i < 4; i++) {
		var trader = traders[i];
		if (trader) {
			trader.update();
		}
	}
	if (planet.name !== PLANET_LAST) {
		updateList(asteroidList);
		trySpawnAsteroid();
		trySpawnTrader();
	}
});
;
game.on('down', function (obj) {
	if (player) {
		var clickPosition = obj.event.getLocalPosition(game);
		var dx = clickPosition.x - planet.x;
		var dy = clickPosition.y - planet.y;
		var range = planet.radius + PLAYER_BUFFER_DIST;
		if (dx * dx + dy * dy <= range * range) {
			resetTasks();
			retargetAngle = Math.atan2(dy, dx) - planet.rotation;
		}
	}
});
;
//==============================================================================
// Global functions
//==============================================================================
;
function updateList(list, idle) {
	for (var i = list.length - 1; i >= 0; i--) {
		var item = list[i];
		if (item.update && item.update(idle)) {
			item.destroy();
			list.splice(i, 1);
		}
	}
}
;
function trySpawnAsteroid() {
	if (Math.random() < ASTEROID_SPAWN_CHANCE) {
		var side = Math.random() < 0.5;
		asteroidList.push(midground.addChild(new Asteroid({
			x: side ? -ASTEROID_MARGIN : GAME_WIDTH + ASTEROID_MARGIN,
			y: Math.random() * GAME_HEIGHT,
			vx: side ? ASTEROID_SPEED : -ASTEROID_SPEED
		})));
	}
}
;
function trySpawnTrader() {
	if (--traderCountdown <= 0) {
		traderCountdown = randomInt(TRADER_TIME_MIN, TRADER_TIME_MAX);
		var available = [];
		for (var i = 0; i < 4; i++) {
			if (!traders[i]) {
				available.push(i);
			}
		}
		if (available.length > 0) {
			var spawnIndex = available[Math.floor(Math.random() * available.length)];
			traders[spawnIndex] = background.addChild(new Trader({
				index: spawnIndex,
				x: GAME_WIDTH / 4 * (spawnIndex % 2 === 0 ? 1 : 3),
				y: GAME_HEIGHT / 32 * (spawnIndex < 2 ? 9 : 27)
			}, nextTrade));
			nextTrade = undefined;
		}
	}
}
;
function transitionPlanets() {
	LK.effects.flashScreen(0x000000, 500);
	currentPlanet = destinationPlanet;
	planet.parent.removeChild(planet);
	planet = planetMap[currentPlanet];
	stats.rocketLaunches++;
	asteroidList.forEach(function (asteroid) {
		asteroid.destroy();
	});
	asteroidList = [];
	for (var i = 0; i < 4; i++) {
		var trader = traders[i];
		if (trader) {
			trader.handleTradeCallback(false);
			trader.destroy();
		}
	}
	traders = [];
	if (!planet) {
		var planetClass = PLANET_DETAILS[currentPlanet].blueprint;
		planet = planetMap[currentPlanet] = new planetClass({
			x: GAME_WIDTH / 2,
			y: GAME_HEIGHT / 2
		});
	}
	if (currentPlanet === NAVIGATION[NAVIGATION.length - 1]) {
		if (winningTick < 0) {
			saveStats();
			winningTick = LK.ticks;
		}
		winningMessage = LK.gui.center.addChild(new WinningMessage(0, -100, winningTick));
	} else if (winningMessage) {
		winningMessage.destroy();
		winningMessage = undefined;
	}
	midground.addChild(planet);
	planet.background.addChild(ship);
	ship.x = planet.radius + ROCKET_DIST_REVERSE;
	resetTasks();
	targetAngle = PLAYER_START_ANGLE;
	background.refresh();
	inventory.refreshAllowed();
}
;
function resetTasks() {
	if (taskLaunch) {
		taskLaunch = false;
		navigation.setDestination(currentPlanet);
	}
	taskUproot = false;
	taskPlanter = false;
	if (player) {
		targetAngle = player.angle;
	}
}
;
function saveStats() {
	for (var key in stats) {
		winningStats[key] = stats[key];
	}
}
;
function mod(x, base) {
	return (x % base + base) % base;
}
;
// TODO: Remove
function randomInt(min, max) {
	return min === max ? min : Math.floor(min + Math.random() * (max - min));
}
function randomIntInRange(min, max) {
	return min === max ? min : Math.floor(min + Math.random() * (max - min));
}
function randomFloatInRange(min, max) {
	return min === max ? min : min + Math.random() * (max - min);
}
;
function approxZero(value) {
	return value > -MATH_APPROX_ZERO && value < MATH_APPROX_ZERO;
}
 pixel art of a tiny planet. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
 pixel art of a planet. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
 pixel art of an alien currency symbol. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
 
 pixel art of a planet made of gold ore. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
 
 
 plain black background with stars. 2d repeating Texture.
 
 pixel art of a asteroid. Game asset. 2d. Blank background. High contrast. No shadows.
 pixel art of a cute alien farmer, side view. Game asset. 2d. Blank background. High contrast. No shadows.
 
 pixel art of a rocky explosion.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
 
 pixel art flame particle. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
 
 pixel art of a large white, empty, rectangular, speech bubble. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
 pixel art of a red chevron. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
 Pixel art of yellow grapes. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.