From 3a90bc5375724d520a05e193eeee6c9a721a46c5 Mon Sep 17 00:00:00 2001
From: echicken <>
Date: Mon, 4 May 2020 04:02:44 +0000
Subject: [PATCH] Added mouse support: Hit a skill key, then click on a lemon
 to assign the skill. (You can also click on nuke, help, pause, quit (I think)
 during play and it will do the thing.) Changed keyboard controls: Now you
 must hit a skill key to select the current skill, then put the cursor over a
 lemon and hit enter or space to assign the skill. The help screen still needs
 to be updated.

---
 xtrn/lemons/defs.js   |  10 +-
 xtrn/lemons/game.js   | 230 +++--------
 xtrn/lemons/lemons.js | 192 +++------
 xtrn/lemons/level.js  | 906 +++++++++++++++++++-----------------------
 4 files changed, 538 insertions(+), 800 deletions(-)

diff --git a/xtrn/lemons/defs.js b/xtrn/lemons/defs.js
index 8d02df2e45..ab42df33dc 100644
--- a/xtrn/lemons/defs.js
+++ b/xtrn/lemons/defs.js
@@ -42,4 +42,12 @@ const	KEY_BASH = "A",
 		KEY_BOMB = "F",
 		KEY_BUILD = "V",
 		KEY_CLIMB = "C",
-		KEY_DIG = "D";
\ No newline at end of file
+		KEY_DIG = "D";
+
+const COLOUR = {};
+COLOUR[KEY_BASH] = COLOUR_BASHER;
+COLOUR[KEY_BLOCK] = COLOUR_BLOCKER;
+COLOUR[KEY_BOMB] = COLOUR_BOMBER;
+COLOUR[KEY_BUILD] = COLOUR_BUILDER;
+COLOUR[KEY_CLIMB] = COLOUR_CLIMBER;
+COLOUR[KEY_DIG] = COLOUR_DIGGER;
\ No newline at end of file
diff --git a/xtrn/lemons/game.js b/xtrn/lemons/game.js
index 39602d091f..d64ee73b85 100644
--- a/xtrn/lemons/game.js
+++ b/xtrn/lemons/game.js
@@ -1,47 +1,24 @@
 // The Game object tracks a user's progress and score across multiple levels
-var Game = function(host, port) {
+var Game = function (host, port) {
 
-	// We want the user to start by choosing an initial level, if applicable.
 	var gameState = GAME_STATE_CHOOSELEVEL;
-
-	// Some values to be tracked throughout this gaming session
 	var stats = {
-		'level' : 0, // Dummy value
-		'score' : 0, // Index into the 'levels' array (see below)
-		'turns' : 5  // How many turns to start the player off with
+		level: 0, // Dummy value
+		score: 0, // Index into the 'levels' array (see below)
+		turns: 5, // How many turns to start the player off with
 	};
-
-	// Just an initial value
-	var levelState = LEVEL_CONTINUE;
-
-	// This will always either be false or an instance of PopUp (lemons.js)
 	var popUp = false;
-
-	/*	This will always either be false (if not paused) or an array of stored
-		Timer events (if paused). */
 	var events = false;
-
-	// Create a DBHelper object, which we'll use for a few things later
+	var levelChooser = false;
+	var levelState = LEVEL_CONTINUE;
 	var dbHelper = new DBHelper(host, port);
-
-	// Load the levels from the DB (or local file)
 	var levels = dbHelper.getLevels();
-
-	// Get this user's highest-achieved level
 	var l = dbHelper.getHighestLevel();
+	if (l >= levels.length) l = levels.length - 1;
 
-	// This is unnecessary except in a very edge case
-	if(l >= levels.length)
-		l = levels.length - 1;
-
-	// This will be a LevelChooser if we're picking a level
-	var levelChooser = false;
-
-	// Make the paused/unpaused status of this Game publically accessible
 	this.paused = false;
 
-	// A custom prompt that works with this game's input & state model
-	var LevelChooser = function() {
+	var LevelChooser = function () {
 
 		var lcFrame = new Frame(
 			frame.x + Math.ceil((frame.width - 32) / 2),
@@ -76,59 +53,28 @@ var Game = function(host, port) {
 		var input = (l + 1) + "";
 		inputFrame.putmsg(input);
 
-		this.getcmd = function(userInput) {
-			
-			// We're only looking for numbers, backspace, or enter
-			if(	userInput == ""
-				||
-				(	userInput != "\x08"
-					&&
-					userInput != "\r"
-					&&
-					userInput != "\n"
-					&&
-					isNaN(parseInt(userInput))
-				)
-			) {
+		this.getcmd = function (userInput) {
+			if (userInput.key == "" || (userInput.key != "\x08" && userInput.key != "\r" && userInput.key != "\n" && isNaN(parseInt(userInput.key)))) {
 				return;
 			}
-
-			// Handle backspace
-			if(userInput == "\x08" && input.length > 0) {
-			
+			if (userInput.key == "\x08" && input.length > 0) {
 				input = input.substr(0, input.length - 1);
-			
-			// Handle enter
-			} else if(userInput == "\r" || userInput == "\n") {
-			
+			} else if (userInput.key == "\r" || userInput.key == "\n") {
 				var ret = parseInt(input);
-				
 				input = "";
 				inputFrame.clear();
 				inputFrame.putmsg(input);
-
-				// Don't return an invalid number
-				if(input.length > 3 || isNaN(ret) || ret > (l + 1) || ret < 1)
-					return;
-				else
-					return (ret - 1);
-			
-			// Anything else that's gotten this far can be appended
+				if (input.length > 3 || isNaN(ret) || ret > (l + 1) || ret < 1) return;
+				return (ret - 1);
 			} else {
-			
-				input += userInput;
-			
+				input += userInput.key;
 			}
-
-			// If we've gotten this far, the input box needs a refresh
 			inputFrame.clear();
 			inputFrame.putmsg(input);
-
 			return;
-
 		}
 
-		this.close = function() {
+		this.close = function () {
 			lcFrame.delete();
 		}
 
@@ -136,183 +82,113 @@ var Game = function(host, port) {
 
 	}
 
-	var level; // We'll need this variable within some of the following methods
+	var level;
 
 	// Handle user input passed to us by the parent script	
-	this.getcmd = function(userInput) {
-
-		// If the user should be choosing a level ...
-		if(gameState == GAME_STATE_CHOOSELEVEL) {
-		
-			// If this is a new user or they haven't beat level 0, start there
-			if(l == 0) {
-	
+	this.getcmd = function (userInput) {
+		if (gameState == GAME_STATE_CHOOSELEVEL) {
+			if (l == 0) {	
 				level = new Level(levels[stats.level], stats.level);
 				gameState = GAME_STATE_NORMAL;
-	
-			// Otherwise let them start at up to their highest level
-			} else if(!levelChooser) {
-
+			} else if (!levelChooser) {
 				levelChooser = new LevelChooser();
-
-			// And if a chooser is already open, use it
 			} else {
-
 				var ll = levelChooser.getcmd(userInput);
-				// The chooser should return a valid level number, or nothing
-				if(typeof ll != "undefined") {
-
+				if (typeof ll != "undefined") {
 					levelChooser.close();
 					levelChooser = false;
 					stats.level = ll;
 					level = new Level(levels[stats.level], stats.level);
 					gameState = GAME_STATE_NORMAL;
 					return true;
-
 				}
-
 			}
-		
-		/*	If gameplay is in progress, pass user input to the Level object.
-			If Level.getcmd returns false, the user hit 'Q' to quit. */
-		} else if(gameState == GAME_STATE_NORMAL && !level.getcmd(userInput)) {
-		
+		} else if (gameState == GAME_STATE_NORMAL && !level.getcmd(userInput)) {
 			level.close();
 			return false;
-		
-		//	If a pop-up is on the screen, remove it if the user hits a key
-		} else if(gameState == GAME_STATE_POPUP && userInput != "") {
-		
+		} else if (gameState == GAME_STATE_POPUP && (userInput.key != "" || userInput.mouse !== null)) {
 			gameState = GAME_STATE_NORMAL;
 			popUp.close();
-		
 		}
-		
-		// Game.getcmd will return true unless the user hit 'Q' during gameplay
 		return true;
-	
 	}
 
-	/*	This is called during the main loop when in gameplay state, and returns
-		true unless the user runs out of turns or beats the game. */
-	this.cycle = function() {
-
-		// If we're waiting for the player to choose a level
-		if(gameState == GAME_STATE_CHOOSELEVEL) {
-
+	this.cycle = function () {
+		if (gameState == GAME_STATE_CHOOSELEVEL) {
 			return true;
-
-		// If we're waiting for them to dismiss a pop-up message
-		} else if(gameState == GAME_STATE_POPUP) {
-
+		} else if (gameState == GAME_STATE_POPUP) {
 			return true;
-
-		// If they failed to rescue enough lemons, or ran out of time
-		} else if(levelState == LEVEL_DEAD || levelState == LEVEL_TIME) {
-
-			// Close the level, perchance to reopen it and start again
+		} else if (levelState == LEVEL_DEAD || levelState == LEVEL_TIME) {
 			level.close();
-			// If the user has run out of turns ...
-			if(stats.turns < 1) {
+			if (stats.turns < 1) {
 				stats.score = stats.score + level.score;
 				return false;
-			// If the user has turns left, let them try again
 			} else {
 				level = new Level(levels[stats.level], stats.level);
 			}
-
-		// If they beat the level, move on to the next one if it exists
-		} else if(levelState == LEVEL_NEXT) {
-
+		} else if (levelState == LEVEL_NEXT) {
 			level.close();
-			if(stats.level < levels.length - 1) {
+			if (stats.level < levels.length - 1) {
 				stats.level++;
 				level = new Level(levels[stats.level], stats.level);
 			} else {
 				return false;
 			}
-
 		}
 
-		// Call the Level.cycle housekeeping method, respond as necessary
 		levelState = level.cycle();
-		switch(levelState) {
-
-			// The user failed to rescue enough lemons.  Raise a pop-up.
+		switch (levelState) {
 			case LEVEL_DEAD:
 				stats.turns--;
-				popUp = new PopUp(
-					[	"What a sour outcome - less than 50% of your lemons survived!",
-						"Score: " + stats.score,
-						stats.turns + " turns remaining",
-						"[ Press any key to continue ]"
-					]
-				);
+				popUp = new PopUp([
+					"What a sour outcome - less than 50% of your lemons survived!",
+					"Score: " + stats.score,
+					stats.turns + " turns remaining",
+					"[ Press any key to continue ]"
+				]);
 				gameState = GAME_STATE_POPUP;
 				break;
-
-			// The user ran out of time.  Raise a pop-up.
 			case LEVEL_TIME:
 				stats.turns--;
-				popUp = new PopUp(
-					[	"You ran out of time!  Now the lemons are going to miss their party.",
-						"Score: " + stats.score,
-						stats.turns + " turns remaining",
-						"[ Press any key to continue ]"
-					]
-				);
+				popUp = new PopUp([
+					"You ran out of time!  Now the lemons are going to miss their party.",
+					"Score: " + stats.score,
+					stats.turns + " turns remaining",
+					"[ Press any key to continue ]"
+				]);
 				gameState = GAME_STATE_POPUP;
 				break;
-
-			// The user beat the level.  Raise a pop-up.
 			case LEVEL_NEXT:
 				stats.score += level.score;
-				popUp = new PopUp(
-					[	"You saved some lemons.  Savour this pointless victory!",
-						"Score: " + stats.score,
-						"[ Press any key to continue ]"
-					]
-				);
+				popUp = new PopUp([
+					"You saved some lemons.  Savour this pointless victory!",
+					"Score: " + stats.score,
+					"[ Press any key to continue ]"
+				]);
 				gameState = GAME_STATE_POPUP;
 				break;
-
-			// The level can continue cycling.  Don't do shit.
 			case LEVEL_CONTINUE:
 				break;
-
 			default:
 				break;
-
 		}
 
-		return true; // Everything's satisfactory in the neighbourhood
+		return true;
 
 	}
 
-	// Pause or resume the level
-	this.pause = function() {
-
-		/*	If the level isn't paused, pause it and store the timed events
-			that it returns. */
-		if(!events) {
-
+	this.pause = function () {
+		if (!events) {
 			events = level.pause();
 			this.paused = true;
-
-		/*	If the level is paused, unpause it by passing in the array of
-			stored events. */
 		} else {
-
 			level.pause(events);
 			events = false;
 			this.paused = false;
-
 		}
-
 	}
 
-	// If the user is done with this gaming session, save their score
-	this.close = function() {
+	this.close = function () {
 		dbHelper.addScore(stats.score, stats.level);
 		dbHelper.close();
 	}
diff --git a/xtrn/lemons/lemons.js b/xtrn/lemons/lemons.js
index 390c14cece..7b74f916cf 100644
--- a/xtrn/lemons/lemons.js
+++ b/xtrn/lemons/lemons.js
@@ -5,8 +5,9 @@ load('tree.js');
 load('event-timer.js');
 load('json-client.js');
 load('sprite.js');
+require("mouse_getkey.js", "mouse_getkey");
+var ansi = load({}, 'ansiterm_lib.js');
 
-// Lemons modules
 load(js.exec_dir + "defs.js");
 load(js.exec_dir + "game.js");
 load(js.exec_dir + "level.js");
@@ -14,45 +15,39 @@ load(js.exec_dir + "menu.js");
 load(js.exec_dir + "help.js");
 load(js.exec_dir + "dbhelper.js");
 
-var	status,		// A place to store bbs.sys_status until exit
-	attributes;	// A place to store console attributes until exit
+var state,
+	frame;
 
-// The Lemons modules expect these globals:
-var state, // What we're currently doing
-	frame; // The parent frame
-
-/*	Draw a border of colour 'color' inside of a frame.
-	'color' can be a number or an array of colours. */
-Frame.prototype.drawBorder = function(color) {
+Frame.prototype.drawBorder = function (color) {
 	var theColor = color;
-	if(Array.isArray(color));
-		var sectionLength = Math.round(this.width / color.length);
+	if (Array.isArray(color)) var sectionLength = Math.round(this.width / color.length);
 	this.pushxy();
-	for(var y = 1; y <= this.height; y++) {
-		for(var x = 1; x <= this.width; x++) {
-			if(x > 1 && x < this.width && y > 1 && y < this.height)
-				continue;
+	for (var y = 1; y <= this.height; y++) {
+		for (var x = 1; x <= this.width; x++) {
+			if (x > 1 && x < this.width && y > 1 && y < this.height) continue;
 			var msg;
 			this.gotoxy(x, y);
-			if(y == 1 && x == 1)
+			if (y == 1 && x == 1) {
 				msg = ascii(218);
-			else if(y == 1 && x == this.width)
+			} else if(y == 1 && x == this.width) {
 				msg = ascii(191);
-			else if(y == this.height && x == 1)
+			} else if(y == this.height && x == 1) {
 				msg = ascii(192);
-			else if(y == this.height && x == this.width)
+			} else if(y == this.height && x == this.width) {
 				msg = ascii(217);
-			else if(x == 1 || x == this.width)
+			} else if(x == 1 || x == this.width) {
 				msg = ascii(179);
-			else
+			} else {
 				msg = ascii(196);
-			if(Array.isArray(color)) {
-				if(x == 1)
+			}
+			if (Array.isArray(color)) {
+				if (x == 1) {
 					theColor = color[0];
-				else if(x % sectionLength == 0 && x < this.width)
+				} else if (x % sectionLength == 0 && x < this.width) {
 					theColor = color[x / sectionLength];
-				else if(x == this.width)
+				} else if (x == this.width) {
 					theColor = color[color.length - 1];
+				}
 			}
 			this.putmsg(msg, theColor);
 		}
@@ -60,16 +55,11 @@ Frame.prototype.drawBorder = function(color) {
 	this.popxy();
 }
 
-/*	Pop a message up near the centre of frame 'frame':
-	var popUp = new PopUp(["Line 1", "Line 2" ...]);
-	console.getkey();
-	popUp.close(); */
-var PopUp = function(message) {
+function PopUp(message) {
 
 	var longestLine = 0;
-	for(var m = 0; m < message.length; m++) {
-		if(message[m].length > longestLine)
-			longestLine = message[m].length;
+	for (var m = 0; m < message.length; m++) {
+		if (message[m].length > longestLine) longestLine = message[m].length;
 	}
 
 	this.frame = new Frame(
@@ -84,25 +74,29 @@ var PopUp = function(message) {
 	this.frame.open();
 	this.frame.drawBorder([CYAN, LIGHTCYAN, WHITE]);
 	this.frame.gotoxy(1, 2);
-	for(var m = 0; m < message.length; m++)
+	for (var m = 0; m < message.length; m++) {
 		this.frame.center(message[m] + "\r\n");
+	}
 
-	this.close = function() {
+	this.close = function () {
 		this.frame.delete();
 	}
 
 }
 
-// Prepare the display, set the initial state
-var init = function() {
-	
-	status = bbs.sys_status; // We'll restore this on cleanup
-	bbs.sys_status|=SS_MOFF; // Turn off node message delivery
+function mouse_enable(enable) {
+	if (!console.term_supports(USER_ANSI)) return;
+	ansi.send('mouse', enable ? 'set' : 'clear', 'x10_compatible');
+	ansi.send('mouse', enable ? 'set' : 'clear', 'extended_coord');
+}
 
-	attributes = console.attributes; // We'll restore this on cleanup
-	console.clear(BG_BLACK|WHITE); // Wipe away any poops
-	
-	// Set up the root frame
+function init() {
+	js.on_exit('frame.delete();');
+	js.on_exit('bbs.sys_status = ' + bbs.sys_status + ';');
+	js.on_exit('console.clear(' + console.attributes + ');');
+	js.on_exit('mouse_enable(false);'); // Should probably depend on whether it's already enabled
+	bbs.sys_status|=SS_MOFF; // Turn off node message delivery
+	console.clear(BG_BLACK|WHITE);
 	frame = new Frame(
 		Math.ceil((console.screen_columns - 79) / 2),
 		Math.ceil((console.screen_rows - 23) / 2),
@@ -112,21 +106,11 @@ var init = function() {
 	);
 	frame.open();
 	frame.checkbounds = false;
-
-	// We start off at the menu screen
 	state = STATE_MENU;
-
-}
-
-// Return the display & system status to normal
-var cleanUp = function() {
-	frame.delete();
-	bbs.sys_status = status;
-	console.clear(attributes);
+	mouse_enable(true);
 }
 
-// Get input from the user, make things happen
-var main = function() {
+function main() {
 
 	// These will always be either 'false' or an instance of a Lemons module
 	var game = false,
@@ -134,130 +118,86 @@ var main = function() {
 		help = false,
 		scoreboard = false;
 
-	if(file_exists(js.exec_dir + "server.ini")) {
+	if (file_exists(js.exec_dir + "server.ini")) {
 		var f = new File(js.exec_dir + "server.ini");
 		f.open("r");
 		var serverIni = f.iniGetObject();
 		f.close();
 	} else {
-		var serverIni = { 'host' : "127.0.0.1", 'port' : 10088 };
+		var serverIni = {
+			host: "127.0.0.1",
+			port: 10088
+		};
 	}
 
-	while(!js.terminated) {
+	while (!js.terminated) {
 
-		// Refresh the user's terminal and bury the (real) cursor if necessary
-		if(frame.cycle())
-			console.gotoxy(console.screen_columns, console.screen_rows);
+		if (frame.cycle()) console.gotoxy(console.screen_columns, console.screen_rows);
 
-		/*	This is the only place where we take input from the user.
-			Input is passed to the current (as per state) module from here. */
-		var userInput = console.inkey(K_UPPER, 5);
-
-		switch(state) {
+		var userInput = mouse_getkey(K_NONE, 5, true);
+		userInput.key = userInput.key.toUpperCase();
 
+		switch (state) {
 			case STATE_MENU:
-				// Load the menu if it isn't already showing
-				if(!menu)
-					menu = new Menu();
-				// Pass user input to the menu
-				menu.getcmd(userInput);
-				// If we've left the menu, close and falsify it
-				if(state != STATE_MENU) {
+				if (!menu) menu = new Menu();
+				menu.getcmd(userInput.key);
+				if (state != STATE_MENU) {
 					menu.close();
 					menu = false;
 				}
 				break;
-
 			case STATE_PLAY:
-				// Create a new Game if we're not already in one
-				if(!game)
-					game = new Game(serverIni.host, serverIni.port);
-				// Pass user input to the game module
-				if(!game.getcmd(userInput)) {
-					// If Game.getcmd returns false, the user has hit 'Q'
+				if (!game) game = new Game(serverIni.host, serverIni.port);
+				if (!game.getcmd(userInput)) {
 					game.close();
 					game = false;
-					// Return to the menu on the next loop
 					state = STATE_MENU;
-				} else if(!game.cycle()) {
-					/*	If Game.cycle returns false, the user is out of turns
-						or has beat the last level. */
+				} else if (!game.cycle()) {
 					game.close();
 					game = false;
-					// Return to the menu on the next loop
 					state = STATE_MENU;
 				}
 				break;
-
 			case STATE_PAUSE:
-				// If the game isn't paused, pause it
-				if(!game.paused)
+				if (!game.paused) game.pause();
+				if (game.paused && (userInput.key != "" || userInput.mouse !== null)) {
 					game.pause();
-				// If the user hit a key and the game is paused, unpause it
-				if(userInput != "" && game.paused) {
-					game.pause();
-					// Return to gameplay on the next loop
 					state = STATE_PLAY;
 				}
 				break;
-
 			case STATE_HELP:
-				// If a game is in progress, pause it
-				if(game instanceof Game && !game.paused)
-					game.pause();
-				// If the help screen isn't showing, load it
-				if(!help)
-					help = new Help();
-				/*	If a game is in progress and the user hit a key, unpause
-					the game, remove the help screen, and return to gameplay. */
-				if(	userInput != ""
-					&&
-					game instanceof Game && game.paused
-				) {
+				if (game instanceof Game && !game.paused) game.pause();
+				if (!help) help = new Help();
+				if (userInput.key != "" && game instanceof Game && game.paused) {
 					help.close();
 					help = false;
 					game.pause();
 					state = STATE_PLAY;
-				/*	If there's no game in progress, close the help screen and
-					return to the menu.	*/
-				} else if(userInput != "") {
+				} else if (userInput.key != "") {
 					help.close();
 					help = false;
 					state = STATE_MENU;
 				}
 				break;
-
 			case STATE_SCORES:
-				// Bring up the scoreboard if it isn't showing already
-				if(!scoreboard) {
+				if (!scoreboard) {
 					scoreboard = new DBHelper(serverIni.host, serverIni.port);
 					scoreboard.showScores();
-				// Remove the scoreboard when the user hits a key
-				} else if(userInput != "") {
+				} else if (userInput.key != "") {
 					scoreboard.close();
 					state = STATE_MENU;
 					scoreboard = false;
 				}
 				break;
-
 			case STATE_EXIT:
-				// Break the main loop
 				return;
-				break; // I just can't not.  :|
-
 			default:
 				break;
-
 		}
 
 	}
 
 }
 
-// Prepare the things
 init();
-// Do the things
-main();
-// Remove the things
-cleanUp();
-// We're done
+main();
\ No newline at end of file
diff --git a/xtrn/lemons/level.js b/xtrn/lemons/level.js
index a3530db2ad..1397e8a977 100644
--- a/xtrn/lemons/level.js
+++ b/xtrn/lemons/level.js
@@ -1,68 +1,63 @@
 // This is the actual game.  It's a disaster, but it gets the job done.
-var Level = function(l, n) {
+var Level = function (l, n) {
 
 	// Scope some variables for use by functions and methods in this object
-	var timer = new Timer(), // A Timer
-		// A place to organize this level's frames
+	var timer = new Timer(),
 		frames = {
-			'counters' : {}
+			labels: {},
+			counters: {}
 		},
 		cursor,		// The 1x1 cursor frame
 		countDown,	// Level timeout
 		lost = 0,	// Lemons lost
 		saved = 0,	// Lemons saved
 		total = 0,	// Total lemon count for this level
-		// Placeholder values for how many of each skill may be assigned
-		quotas = {
-			'basher' : 0,
-			'blocker' : 0,
-			'bomber' : 0,
-			'builder' : 0,
-			'climber' : 0,
-			'digger' : 0
-		},
-		nuked = false;
+		nuked = false,
+		currentSkill = null,
+		quotas = {};
+	quotas[KEY_BASH] = 0;
+	quotas[KEY_BLOCK] = 0;
+	quotas[KEY_BOMB] = 0;
+	quotas[KEY_BUILD] = 0;
+	quotas[KEY_CLIMB] = 0;
+	quotas[KEY_DIG] = 0;
 
 	// The parent Game object will want to read this
 	this.score = 0;
 
 	/*	Change the colour of a lemon's peel without altering the colour of its
 		shoes or 'nuked' position. */
-	var colorize = function(sprite, colour) {
+	function colorize(sprite, colour) {
 		var fgmask = (1<<0)|(1<<1)|(1<<2)|(1<<3);
-		for(var y = 0; y < sprite.frame.data.length; y++) {
-			for(var x = 0; x < 9; x++) {
+		for (var y = 0; y < sprite.frame.data.length; y++) {
+			for (var x = 0; x < 9; x++) {
 				sprite.frame.data[y][x].attr&=~fgmask;
-				if((y == 2 || y == 5) && (x == 0 || x == 2 || x == 3 || x == 5 || x == 6 || x == 8))
+				if ((y == 2 || y == 5) && (x == 0 || x == 2 || x == 3 || x == 5 || x == 6 || x == 8)) {
 					sprite.frame.data[y][x].attr=BG_BLACK|BROWN;
-				else
+				} else {
 					sprite.frame.data[y][x].attr|=colour;
+				}
 			}
 		}
 		sprite.frame.invalidate();
 	}
 
 	//	Turn skilled lemon back into an ordinary zombie-lemon.
-	var lemonize = function(sprite) {
+	function lemonize(sprite) {
 		colorize(sprite, COLOUR_LEMON);
 		sprite.ini.constantmotion = 1;
 		sprite.ini.gravity = 1;
 		sprite.ini.speed = .25;
 		sprite.ini.skill = "lemon";
-		if(typeof sprite.ini.buildCount != "undefined")
-			delete sprite.ini.buildCount;
-		if(typeof sprite.ini.lastBuild != "undefined")
-			delete sprite.ini.lastBuild;
-		if(typeof sprite.ini.climbStart != "undefined")
-			delete sprite.ini.climbStart;
-		if(typeof sprite.ini.lastDig != "undefined")
-			delete sprite.ini.lastDig;
+		if (typeof sprite.ini.buildCount != "undefined") delete sprite.ini.buildCount;
+		if (typeof sprite.ini.lastBuild != "undefined") delete sprite.ini.lastBuild;
+		if (typeof sprite.ini.climbStart != "undefined") delete sprite.ini.climbStart;
+		if (typeof sprite.ini.lastDig != "undefined") delete sprite.ini.lastDig;
 	}
 
 	// Remove the lemon from the screen
-	var remove = function(sprite) {
-		if(!sprite.open)
-			return;
+	function remove(sprite) {
+		if (!sprite.open) return;
 		sprite.remove();
 	}
 
@@ -72,41 +67,22 @@ var Level = function(l, n) {
 		on top of it (stairs, etc.)
 		Normal, bomber, and builder lemons should be passed to this function.
 	*/
-	var turnOrClimbIfObstacle = function(sprite) {
+	function turnOrClimbIfObstacle(sprite) {
 
 		var beside = (sprite.bearing == "w") ? Sprite.checkLeft(sprite) : Sprite.checkRight(sprite);
-		if(!beside)
-			beside = Sprite.checkOverlap(sprite);
-		if(!beside)
-			return;
+		if (!beside) beside = Sprite.checkOverlap(sprite);
+		if (!beside) return;
 
-		for(var b = 0; b < beside.length; b++) {
+		for (var b = 0; b < beside.length; b++) {
 
-			if(	beside[b].ini.type != "block"
-				&&
-				beside[b].ini.type != "lemon"
-				&&
-				beside[b].ini.type != "hazard"
-			) {
-				continue;
-			}
+			if (beside[b].ini.type != "block" && beside[b].ini.type != "lemon" && beside[b].ini.type != "hazard") continue;
 
-			if(	beside[b].ini.type == "block"
-				&&
-				beside[b].y == sprite.y + sprite.ini.height - 1
-			) {
-				sprite.moveTo(
-					(sprite.bearing == "w") ? (sprite.x - 1) : (sprite.x + 1),
-					sprite.y - 1
-				);
-				if(Sprite.checkOverlap(sprite)) {
-					sprite.moveTo(
-						(sprite.bearing == "w") ? (sprite.x + 1) : (sprite.x - 1),
-						sprite.y + 1
-					);
+			if (beside[b].ini.type == "block" && beside[b].y == sprite.y + sprite.ini.height - 1) {
+				sprite.moveTo((sprite.bearing == "w") ? (sprite.x - 1) : (sprite.x + 1), sprite.y - 1);
+				if (Sprite.checkOverlap(sprite)) {
+					sprite.moveTo((sprite.bearing == "w") ? (sprite.x + 1) : (sprite.x - 1), sprite.y + 1);
 					sprite.turnTo((sprite.bearing == "w") ? "e" : "w");
-					if(sprite.ini.skill == "builder")
-						lemonize(sprite);
+					if (sprite.ini.skill == "builder") lemonize(sprite);
 				}
 				sprite.lastMove = system.timer;
 				break;
@@ -115,29 +91,23 @@ var Level = function(l, n) {
 				sprite.turnTo((sprite.bearing == "w") ? "e" : "w");
 				sprite.lastMove = system.timer;
 				var overlaps = Sprite.checkOverlap(sprite);
-				if(overlaps) {
-					for(var o = 0; o < overlaps.length; o++) {
-						if(	overlaps[o].ini.type != "block"
-							&&
-							overlaps[o].ini.type != "lemon"
-							&&
-							overlaps[o].ini.type != "hazard"
-						) {
+				if (overlaps) {
+					for (var o = 0; o < overlaps.length; o++) {
+						if (overlaps[o].ini.type != "block" && overlaps[o].ini.type != "lemon" && overlaps[o].ini.type != "hazard") {
 							continue;
 						}
-						if(sprite.bearing == "w") {
-							while(sprite.x < overlaps[o].x + overlaps[o].frame.width) {
+						if (sprite.bearing == "w") {
+							while (sprite.x < overlaps[o].x + overlaps[o].frame.width) {
 								sprite.move("reverse");
 							}
-						} else if(sprite.bearing == "e") {
-							while(sprite.x + sprite.ini.width > overlaps[o].x) {
+						} else if (sprite.bearing == "e") {
+							while (sprite.x + sprite.ini.width > overlaps[o].x) {
 								sprite.move("reverse");
 							}
 						}
 					}
 				}
-				if(sprite.ini.skill == "builder")
-					lemonize(sprite);
+				if (sprite.ini.skill == "builder") lemonize(sprite);
 				break;
 			}
 
@@ -151,74 +121,53 @@ var Level = function(l, n) {
 		was just a single block (stairs, perhaps) and the 'climber' skill
 		is retained for later use.  Otherwise convert back to normal lemon
 		status. */
-	var climbIfObstacle = function(sprite) {
+	function climbIfObstacle(sprite) {
 
-		if(system.timer - sprite.lastYMove < sprite.ini.speed)
-			return;
+		if (system.timer - sprite.lastYMove < sprite.ini.speed) return;
 
-		if(Sprite.checkAbove(sprite)) {
+		if (Sprite.checkAbove(sprite)) {
 			lemonize(sprite);
 			sprite.lastMove = system.timer;
 			return;
 		}
 
 		var beside = (sprite.bearing == "w") ? Sprite.checkLeft(sprite) : Sprite.checkRight(sprite);
-		if(!beside)
-			beside = Sprite.checkOverlap(sprite);
-		if(!beside)
-			return;
-
-		if(typeof sprite.ini.climbStart == "undefined")
-			sprite.ini.climbStart = sprite.y;
+		if (!beside) beside = Sprite.checkOverlap(sprite);
+		if (!beside) return;
 
+		if (typeof sprite.ini.climbStart == "undefined") sprite.ini.climbStart = sprite.y;
 		sprite.ini.gravity = 0;
 		sprite.ini.constantmotion = 0;
-
-		for(var b = 0; b < beside.length; b++) {
-
-			if(	beside[b].ini.type == "entrance"
-				||
-				beside[b].ini.type == "exit"
-				||
-				beside[b].ini.type == "projectile"
-			) {
+		for (var b = 0; b < beside.length; b++) {
+			if (beside[b].ini.type == "entrance" || beside[b].ini.type == "exit" || beside[b].ini.type == "projectile") {
 				lemonize(sprite);
 				return;
-			} else if(beside[b].ini.type == "lemon") {
+			} else if (beside[b].ini.type == "lemon") {
 				sprite.turnTo((sprite.bearing == "w") ? "e" : "w");
 				sprite.ini.gravity = 1;
 				sprite.ini.constantmotion = 1;
 				return;
 			}
-
 		}
 
-		sprite.moveTo(
-			(sprite.bearing == "w") ? (sprite.x - 1) : (sprite.x + 1),
-			sprite.y - 1
-		);
+		sprite.moveTo((sprite.bearing == "w") ? (sprite.x - 1) : (sprite.x + 1), sprite.y - 1);
 		sprite.lastYMove = system.timer;
 		var overlaps = Sprite.checkOverlap(sprite);
-		if(overlaps) {
-			for(var o = 0; o < overlaps.length; o++) {
-				if(overlaps[o].y != sprite.y + sprite.ini.height - 1)
-					continue;
-				if(sprite.bearing == "w") {
-					while(sprite.x < overlaps[o].x + overlaps[o].frame.width) {
+		if (overlaps) {
+			for (var o = 0; o < overlaps.length; o++) {
+				if (overlaps[o].y != sprite.y + sprite.ini.height - 1) continue;
+				if (sprite.bearing == "w") {
+					while (sprite.x < overlaps[o].x + overlaps[o].frame.width) {
 						sprite.move("reverse");
 					}
-				} else if(sprite.bearing == "e") {
-					while(sprite.x + sprite.ini.width > overlaps[o].x) {
+				} else if (sprite.bearing == "e") {
+					while (sprite.x + sprite.ini.width > overlaps[o].x) {
 						sprite.move("reverse");
 					}
 				}
 				break;
 			}
-		} else if(
-			sprite.ini.climbStart > sprite.y
-			&&
-			sprite.ini.climbStart - sprite.y > 1
-		) {
+		} else if (sprite.ini.climbStart > sprite.y && sprite.ini.climbStart - sprite.y > 1) {
 			lemonize(sprite);
 		} else {
 			sprite.ini.constantmotion = 1;
@@ -232,36 +181,25 @@ var Level = function(l, n) {
 		for us.  We just need to enforce straight vertical drops, and make
 		sure certain types don't start moving again once they land. 
 		All lemons should be passed to this function.  */
-	var fallIfNoFloor = function(sprite) {
+	function fallIfNoFloor(sprite) {
 
 		if(sprite.inFall && sprite.ini.constantmotion == 1) {
 			// Stop lemons from moving horizontally when falling
 			sprite.ini.constantmotion = 0;
 		} else if(
 			!sprite.inFall
-			&&
-			sprite.ini.constantmotion == 0
-			&&
-			sprite.ini.gravity == 1
-			&&
-			sprite.ini.skill != "blocker"
-			&&
-			sprite.ini.skill != "digger"
-			&&
-			sprite.ini.skill != "basher"
-			&&
-			sprite.ini.skill != "builder"
-			&&
-			sprite.ini.skill != "dying"
+			&& sprite.ini.constantmotion == 0
+			&& sprite.ini.gravity == 1
+			&& sprite.ini.skill != KEY_BLOCK
+			&& sprite.ini.skill != KEY_DIG
+			&& sprite.ini.skill != KEY_BASH
+			&& sprite.ini.skill != KEY_BUILD
+			&& sprite.ini.skill != "dying"
 		) {
 
-			if(typeof sprite.ini.forceDrop == "boolean")
-				delete sprite.ini.forceDrop;
+			if (typeof sprite.ini.forceDrop == "boolean") delete sprite.ini.forceDrop;
 
-			if(	typeof sprite.ini.ticker == "undefined"
-				&&
-				sprite.ini.skill != "climber"
-			) {
+			if(typeof sprite.ini.ticker == "undefined" && sprite.ini.skill != "climber") {
 				lemonize(sprite);
 			} else {
 				sprite.ini.constantmotion = 1;
@@ -273,13 +211,11 @@ var Level = function(l, n) {
 	/*	If a lemon has reached the exit, remove it and update counters
 		accordingly.
 		All but blocker lemons should be passed to this function. */
-	var removeIfAtExit = function(sprite) {
+	function removeIfAtExit(sprite) {
 		var overlaps = Sprite.checkOverlap(sprite);
-		if(!overlaps)
-			return;
-		for(var o = 0; o < overlaps.length; o++) {
-			if(overlaps[o].ini.type != "exit")
-				continue;
+		if (!overlaps) return;
+		for (var o = 0; o < overlaps.length; o++) {
+			if (overlaps[o].ini.type != "exit") continue;
 			sprite.remove();
 			saved++;
 			frames.counters.lostSaved.clear();
@@ -294,15 +230,13 @@ var Level = function(l, n) {
 		It's too easy to just remove an entire block from the screen,
 		so we will produce a nice effect by dismantling the block by
 		one half-cell every ~ .5 seconds. */
-	var bashersGonnaBash = function(sprite) {
+	function bashersGonnaBash(sprite) {
 
-		if(typeof sprite.ini.lastDig != "undefined" && system.timer - sprite.ini.lastDig < .5)
-			return;
+		if (typeof sprite.ini.lastDig != "undefined" && system.timer - sprite.ini.lastDig < .5) return;
 
 		var beside = (sprite.bearing == "e") ? Sprite.checkRight(sprite) : Sprite.checkLeft(sprite);
-		if(!beside)
-			beside = Sprite.checkOverlap(sprite);
-		if(!beside) {
+		if (!beside) beside = Sprite.checkOverlap(sprite);
+		if (!beside) {
 			sprite.ini.constantmotion = 1;
 			return;
 		}
@@ -311,71 +245,56 @@ var Level = function(l, n) {
 
 		var blockFound = false;
 		var columnClear = false;
-		for(var b = 0; b < beside.length; b++) {
+		for (var b = 0; b < beside.length; b++) {
 
-			if(beside[b].ini.type == "lemon") {
+			if (beside[b].ini.type == "lemon") {
 				sprite.turnTo((sprite.bearing == "e") ? "w" : "e");
 				return;
 			}
 
-			if(	beside[b].ini.type != "block"
-				||
-				!beside[b].open
-			) {
-				continue;
-			}
+			if (beside[b].ini.type != "block" || !beside[b].open) continue;
 
-			if(beside[b].y == sprite.y + sprite.ini.height - 1) {
-				sprite.moveTo(
-					(sprite.bearing == "w") ? (sprite.x - 1) : (sprite.x + 1),
-					sprite.y - 1
-				);
-				if(!Sprite.checkOverlap(sprite)) {
+			if (beside[b].y == sprite.y + sprite.ini.height - 1) {
+				sprite.moveTo((sprite.bearing == "w") ? (sprite.x - 1) : (sprite.x + 1), sprite.y - 1);
+				if (!Sprite.checkOverlap(sprite)) {
 					sprite.ini.constantmotion = 1;
 					return;
 				}
-				sprite.moveTo(
-					(sprite.bearing == "w") ? (sprite.x + 1) : (sprite.x - 1),
-					sprite.y + 1
-				);
+				sprite.moveTo((sprite.bearing == "w") ? (sprite.x + 1) : (sprite.x - 1), sprite.y + 1);
 			}
 
 			blockFound = true;
 
-			if(beside[b].ini.material == "metal") {
+			if (beside[b].ini.material == "metal") {
 				lemonize(sprite);
 				sprite.turnTo((sprite.bearing == "e") ? "w" : "e");
 				return;
 			}
 
-			if(sprite.bearing == "e")
+			if (sprite.bearing == "e") {
 				var x = (beside[b].x - sprite.x == 3) ? 0 : ((beside[b].x - sprite.x == 2) ? 1 : 2);
-			else
+			} else {
 				var x = (sprite.x - beside[b].x == 3) ? 2 : ((sprite.x - beside[b].x == 2) ? 1 : 0);
+			}
 
 			beside[b].frame.invalidate();
 			sprite.ini.lastDig = system.timer;
 
-			if(beside[b].frame.data[0][x].ch == ascii(220)) {
+			if (beside[b].frame.data[0][x].ch == ascii(220)) {
 				beside[b].frame.data[0][x].ch = " ";
 				beside[b].frame.data[0][x].attr = BG_BLACK;
-				if(	(sprite.bearing == "e" && x == 2)
-					||
-					(sprite.bearing == "w" && x == 0)
-				) {
-					beside[b].remove();
-				}
+				if(	(sprite.bearing == "e" && x == 2) || (sprite.bearing == "w" && x == 0)) beside[b].remove();
 				columnClear = (b == beside.length - 1);
 				break;
 			}
 
-			if(beside[b].frame.data[0][x].ch == ascii(223)) {
+			if (beside[b].frame.data[0][x].ch == ascii(223)) {
 				beside[b].frame.data[0][x].ch = ascii(220);
 				beside[b].frame.data[0][x].attr = BG_BLACK|BROWN;
 				break;
 			}
 
-			if(beside[b].frame.data[0][x].ch == ascii(219)) {
+			if (beside[b].frame.data[0][x].ch == ascii(219)) {
 				beside[b].frame.data[0][x].ch = ascii(220);
 				beside[b].frame.data[0][x].attr = BG_BLACK|RED;
 				break;
@@ -383,59 +302,47 @@ var Level = function(l, n) {
 
 		}
 
-		if(columnClear) {
+		if (columnClear) {
 			sprite.move("forward");
-			if(	((sprite.bearing == "e" && !Sprite.checkRight(sprite))
-				||
-				(sprite.bearing == "w" && !Sprite.checkLeft(sprite)))
-				&&
-				!Sprite.checkOverlap(sprite)
-			) {
+			if (((sprite.bearing == "e" && !Sprite.checkRight(sprite)) || (sprite.bearing == "w" && !Sprite.checkLeft(sprite))) && !Sprite.checkOverlap(sprite)) {
 				lemonize(sprite);
 				return;
 			}
-		} else if(!blockFound) {
+		} else if (!blockFound) {
 			lemonize(sprite);
 		}
 
 	}
 
 	//	Like bashersGonnaBash, but for digging downward.
-	var diggersGonnaDig = function(sprite) {
+	function diggersGonnaDig(sprite) {
 
-		if(typeof sprite.ini.lastDig != "undefined" && system.timer - sprite.ini.lastDig < .5)
-			return;
+		if (typeof sprite.ini.lastDig != "undefined" && system.timer - sprite.ini.lastDig < .5) return;
 
 		var below = Sprite.checkBelow(sprite);
-		if(!below)
-			below = Sprite.checkOverlap(sprite);
-		if(!below)
-			return;
+		if (!below) below = Sprite.checkOverlap(sprite);
+		if (!below) return;
 
 		sprite.ini.constantmotion = 0;
 
 		var clear = below.length;
-		for(var b = 0; b < below.length; b++) {
+		for (var b = 0; b < below.length; b++) {
 
-			if(	below[b].ini.type != "block"
-				||
-				!below[b].open
-			) {
+			if (below[b].ini.type != "block" || !below[b].open) {
 				clear--;
 				continue;
 			}
 
-			if(below[b].ini.material == "metal") {
+			if (below[b].ini.material == "metal") {
 				lemonize(sprite);
 				return;
 			}
 
 			var cleared = true;
-			for(var c = 0; c < 3; c++) {
-				if(below[b].frame.data[0][c].ch != " ")
-					cleared = false;
+			for (var c = 0; c < 3; c++) {
+				if (below[b].frame.data[0][c].ch != " ") cleared = false;
 			}				
-			if(cleared) {
+			if (cleared) {
 				below[b].remove();
 				clear--;
 				continue;
@@ -444,21 +351,21 @@ var Level = function(l, n) {
 			below[b].frame.invalidate();
 			sprite.ini.lastDig = system.timer;
 
-			for(var c = 0; c < 3; c++) {
+			for (var c = 0; c < 3; c++) {
 
-				if(below[b].frame.data[0][c].ch == ascii(220)) {
+				if (below[b].frame.data[0][c].ch == ascii(220)) {
 					below[b].frame.data[0][c].ch = " ";
 					below[b].frame.data[0][c].attr = 0;
 					break;
 				}
 
-				if(below[b].frame.data[0][c].ch == ascii(223)) {
+				if (below[b].frame.data[0][c].ch == ascii(223)) {
 					below[b].frame.data[0][c].ch = ascii(220);
 					below[b].frame.data[0][c].attr = BG_BLACK|BROWN;
 					break;
 				}
 
-				if(below[b].frame.data[0][c].ch == ascii(219)) {
+				if (below[b].frame.data[0][c].ch == ascii(219)) {
 					below[b].frame.data[0][c].ch = ascii(220);
 					below[b].frame.data[0][c].attr = BG_BLACK|RED;
 					break;
@@ -468,9 +375,9 @@ var Level = function(l, n) {
 
 		}
 
-		if(clear == 0) {
+		if (clear == 0) {
 			sprite.moveTo(sprite.x, sprite.y + 1);
-			if(!Sprite.checkBelow(sprite)) {
+			if (!Sprite.checkBelow(sprite)) {
 				lemonize(sprite);
 				sprite.lastMove = system.timer;
 			}
@@ -479,17 +386,16 @@ var Level = function(l, n) {
 	}
 
 	// Build a staircase four blocks high, or until an obstacle is encountered
-	var buildersGonnaBuild = function(sprite) {
+	function buildersGonnaBuild(sprite) {
 
-		if(typeof sprite.ini.lastBuild == "undefined") {
+		if (typeof sprite.ini.lastBuild == "undefined") {
 			sprite.ini.lastBuild = system.timer;
 			sprite.ini.buildCount = 0;
 			sprite.ini.constantmotion = 0;
 		}
-		if(system.timer - sprite.ini.lastBuild < .5)
-			return;
+		if (system.timer - sprite.ini.lastBuild < .5) return;
 
-		if(sprite.ini.buildCount == 4) {
+		if (sprite.ini.buildCount == 4) {
 			lemonize(sprite);
 			return;
 		}
@@ -506,22 +412,19 @@ var Level = function(l, n) {
 			"normal"
 		);
 		var overlap = Sprite.checkOverlap(block);
-		if(overlap) {
+		if (overlap) {
 			block.remove();
 			Sprite.profiles.splice(block.index, 1);
 			lemonize(sprite);
 			return;
 		}
 		block.frame.open();
-		sprite.moveTo(
-			(sprite.bearing == "e") ? (sprite.x + 3) : (sprite.x - 3),
-			sprite.y - 1
-		);
+		sprite.moveTo((sprite.bearing == "e") ? (sprite.x + 3) : (sprite.x - 3), sprite.y - 1);
 
 	}
 
 	// Draw the time left until explosion at the centre of each of a lemon's positions
-	var ticker = function(sprite) {
+	function ticker(sprite) {
 		sprite.ini.ticker--;
 		sprite.frame.data[1][1].ch = sprite.ini.ticker;
 		sprite.frame.data[1][4].ch = sprite.ini.ticker;
@@ -532,15 +435,13 @@ var Level = function(l, n) {
 	}
 
 	// Make the lemon look explodey, cause some damage
-	var explode = function(sprite) {
-		if(!sprite.open)
-			return;
+	function explode(sprite) {
+		if (!sprite.open) return;
 		sprite.changePosition("nuked");
 		sprite.ini.speed = 2000;
 		var overlap = Sprite.checkOverlap(sprite, 1);
-		for(var o = 0; overlap && o < overlap.length; o++) {
-			if(overlap[o].ini.type == "lemon")
-				continue;
+		for (var o = 0; overlap && o < overlap.length; o++) {
+			if (overlap[o].ini.type == "lemon") continue;
 			overlap[o].remove();
 		}
 		lost++;
@@ -549,7 +450,7 @@ var Level = function(l, n) {
 	}
 
 	// Redden a lemon, and prepare it for obliteration
-	var nuke = function(sprite) {
+	function nuke(sprite) {
 		sprite.ini.ticker = 5;
 		timer.addEvent(5000, false, explode, [sprite]);
 		timer.addEvent(6000, false, remove, [sprite]);
@@ -559,25 +460,16 @@ var Level = function(l, n) {
 	}
 
 	// Make a lemon tap its foot
-	var tapFoot = function(sprite) {
-		sprite.changePosition(
-			(sprite.position == "normal") ? "normal2" : "normal"
-		);
+	function tapFoot(sprite) {
+		sprite.changePosition((sprite.position == "normal") ? "normal2" : "normal");
 	}
 
 	/*	Populate the terminal with a status bar, the level's static sprites,
 		and set up timed events for releasing lemons and level timeout.	*/
-	var loadLevel = function(level, frame) {
+	function loadLevel(level, frame) {
 
 		// The parent frame for all sprites & frames created by Level
-		frames.game = new Frame(
-			frame.x,
-			frame.y,
-			frame.width,
-			frame.height,
-			BG_BLACK|LIGHTGRAY,
-			frame
-		);
+		frames.game = new Frame(frame.x, frame.y, frame.width, frame.height, BG_BLACK|LIGHTGRAY, frame);
 
 		// The gameplay area
 		frames.field = new Frame(
@@ -599,53 +491,107 @@ var Level = function(l, n) {
 			frames.game
 		);
 
-		frames.counters.basher = new Frame(
-			frames.statusBar.x + 10,
+		frames.labels[KEY_BASH] = new Frame(
+			frames.statusBar.x,
+			frames.statusBar.y,
+			9,
+			1,
+			COLOUR_STATUSBAR_BG|COLOUR_BASHER,
+			frames.statusBar
+		);
+
+		frames.counters[KEY_BASH] = new Frame(
+			frames.statusBar.x + 9,
 			frames.statusBar.y,
 			3,
 			1,
 			COLOUR_STATUSBAR_BG|COLOUR_STATUSBAR_FG,
 			frames.statusBar
 		);
+
+		frames.labels[KEY_BOMB] = new Frame(
+			frames.statusBar.x + 13,
+			frames.statusBar.y,
+			9,
+			1,
+			COLOUR_STATUSBAR_BG|COLOUR_BOMBER,
+			frames.statusBar
+		);
 		
-		frames.counters.bomber = new Frame(
-			frames.statusBar.x + 24,
+		frames.counters[KEY_BOMB] = new Frame(
+			frames.statusBar.x + 21,
 			frames.statusBar.y,
 			3,
 			1,
 			COLOUR_STATUSBAR_BG|COLOUR_STATUSBAR_FG,
 			frames.statusBar
 		);
-		
-		frames.counters.climber = new Frame(
-			frames.statusBar.x + 38,
+
+		frames.labels[KEY_CLIMB] = new Frame(
+			frames.statusBar.x + 25,
+			frames.statusBar.y,
+			9,
+			1,
+			COLOUR_STATUSBAR_BG|COLOUR_CLIMBER,
+			frames.statusBar
+		);
+
+		frames.counters[KEY_CLIMB] = new Frame(
+			frames.statusBar.x + 34,
 			frames.statusBar.y,
 			3,
 			1,
 			COLOUR_STATUSBAR_BG|COLOUR_STATUSBAR_FG,
 			frames.statusBar
 		);
+
+		frames.labels[KEY_BLOCK] = new Frame(
+			frames.statusBar.x,
+			frames.statusBar.y + 1,
+			9,
+			1,
+			COLOUR_STATUSBAR_BG|COLOUR_BLOCKER,
+			frames.statusBar
+		);
 		
-		frames.counters.blocker = new Frame(
-			frames.statusBar.x + 10,
+		frames.counters[KEY_BLOCK] = new Frame(
+			frames.statusBar.x + 9,
 			frames.statusBar.y + 1,
 			3,
 			1,
 			COLOUR_STATUSBAR_BG|COLOUR_STATUSBAR_FG,
 			frames.statusBar
 		);
+
+		frames.labels[KEY_BUILD] = new Frame(
+			frames.statusBar.x + 25,
+			frames.statusBar.y + 1,
+			9,
+			1,
+			COLOUR_STATUSBAR_BG|COLOUR_BUILDER,
+			frames.statusBar
+		);
 		
-		frames.counters.builder = new Frame(
-			frames.statusBar.x + 24,
+		frames.counters[KEY_BUILD] = new Frame(
+			frames.statusBar.x + 34,
 			frames.statusBar.y + 1,
 			3,
 			1,
 			COLOUR_STATUSBAR_BG|COLOUR_STATUSBAR_FG,
 			frames.statusBar
 		);
+
+		frames.labels[KEY_DIG] = new Frame(
+			frames.statusBar.x + 13,
+			frames.statusBar.y + 1,
+			9,
+			1,
+			COLOUR_STATUSBAR_BG|COLOUR_DIGGER,
+			frames.statusBar
+		);
 		
-		frames.counters.digger = new Frame(
-			frames.statusBar.x + 38,
+		frames.counters[KEY_DIG] = new Frame(
+			frames.statusBar.x + 21,
 			frames.statusBar.y + 1,
 			3,
 			1,
@@ -680,33 +626,65 @@ var Level = function(l, n) {
 			frames.statusBar
 		);		
 		
-		// Populate the status bar's static text fields
-		frames.statusBar.putmsg(KEY_BASH + ") Bash :     ", COLOUR_BASHER|COLOUR_STATUSBAR_BG);
-		frames.statusBar.putmsg(KEY_BOMB + ") Bomb :     ", COLOUR_BOMBER|COLOUR_STATUSBAR_BG);
-		frames.statusBar.putmsg(KEY_CLIMB + ") Climb:     ", COLOUR_CLIMBER|COLOUR_STATUSBAR_BG);
-		frames.statusBar.putmsg("N)uke   ", COLOUR_NUKED|COLOUR_STATUSBAR_BG);
-		frames.statusBar.putmsg("H)elp  ", WHITE|COLOUR_STATUSBAR_BG);
+		frames.labels[KEY_BASH].putmsg(KEY_BASH + ') Bash');
+		frames.labels[KEY_BOMB].putmsg(KEY_BOMB + ') Bomb');
+		frames.labels[KEY_BLOCK].putmsg(KEY_BLOCK + ') Block');
+		frames.labels[KEY_BUILD].putmsg(KEY_BUILD + ') Build');
+		frames.labels[KEY_CLIMB].putmsg(KEY_CLIMB + ') Climb');
+		frames.labels[KEY_DIG].putmsg(KEY_DIG + ') Dig');
+
+		frames.statusBar.gotoxy(frames.counters[KEY_CLIMB].x + 5 - frames.statusBar.x, 1);
+		frames.statusBar.putmsg("N)uke", COLOUR_NUKED|COLOUR_STATUSBAR_BG);
+		frames.statusBar.gotoxy(frames.counters[KEY_CLIMB].x + 5 - frames.statusBar.x, 2);
+		frames.statusBar.putmsg("P)ause", WHITE|COLOUR_STATUSBAR_BG);
+
+		frames.statusBar.gotoxy(frames.counters[KEY_CLIMB].x + 13 - frames.statusBar.x, 1);
+		frames.statusBar.putmsg("H)elp", WHITE|COLOUR_STATUSBAR_BG);
+		frames.statusBar.gotoxy(frames.counters[KEY_CLIMB].x + 13 - frames.statusBar.x, 2);
+		frames.statusBar.putmsg("Q)uit", WHITE|COLOUR_STATUSBAR_BG);
+
+		frames.statusBar.gotoxy(frames.counters[KEY_CLIMB].x + 24 - frames.statusBar.x, 1);
 		frames.statusBar.putmsg("  Released:       Time:\r\n", WHITE|COLOUR_STATUSBAR_BG);
-		frames.statusBar.putmsg(KEY_BLOCK + ") Block:     ", COLOUR_BLOCKER|COLOUR_STATUSBAR_BG);
-		frames.statusBar.putmsg(KEY_BUILD + ") Build:     ", COLOUR_BUILDER|COLOUR_STATUSBAR_BG);
-		frames.statusBar.putmsg(KEY_DIG + ") Dig  :     ", COLOUR_DIGGER|COLOUR_STATUSBAR_BG);
-		frames.statusBar.putmsg("P)ause  ", WHITE|COLOUR_STATUSBAR_BG);
-		frames.statusBar.putmsg("Q)uit  ", WHITE|COLOUR_STATUSBAR_BG);
+		frames.statusBar.gotoxy(frames.counters[KEY_CLIMB].x + 24 - frames.statusBar.x, 2);
 		frames.statusBar.putmsg("Lost/Saved: ", WHITE|COLOUR_STATUSBAR_BG);
 
 		// Display the initial lost/saved value
 		frames.counters.lostSaved.putmsg(lost + "/" + saved);
-
 		// Set the quota values
-		if(typeof level.quotas == "undefined")
-			level.quotas = {};
-		for(var q in level.quotas) {
-			quotas[q] = level.quotas[q];
-			frames.counters[q].putmsg(quotas[q]);
+		if (typeof level.quotas == "undefined") level.quotas = {};
+		for (var q in level.quotas) {
+			switch (q) {
+				case 'basher':
+					quotas[KEY_BASH] = level.quotas[q];
+					frames.counters[KEY_BASH].putmsg(quotas[KEY_BASH]);
+					break;
+				case 'bomber':
+					quotas[KEY_BOMB] = level.quotas[q];
+					frames.counters[KEY_BOMB].putmsg(quotas[KEY_BOMB]);
+					break;
+				case 'blocker':
+					quotas[KEY_BLOCK] = level.quotas[q];
+					frames.counters[KEY_BLOCK].putmsg(quotas[KEY_BLOCK]);
+					break;
+				case 'builder':
+					quotas[KEY_BUILD] = level.quotas[q];
+					frames.counters[KEY_BUILD].putmsg(quotas[KEY_BUILD]);
+					break;
+				case 'climber':
+					quotas[KEY_CLIMB] = level.quotas[q];
+					frames.counters[KEY_CLIMB].putmsg(quotas[KEY_CLIMB]);
+					break;
+				case 'digger':
+					quotas[KEY_DIG] = level.quotas[q];
+					frames.counters[KEY_DIG].putmsg(quotas[KEY_DIG]);
+					break;
+				default:
+					break;
+			}
 		}
 
 		// Add bricks, etc. to the screen
-		for(var b = 0; b < level.blocks.length; b++) {
+		for (var b = 0; b < level.blocks.length; b++) {
 			new Sprite.Profile(
 				level.blocks[b].type,
 				frames.field,
@@ -718,7 +696,7 @@ var Level = function(l, n) {
 		}
 
 		// Add hazards (water, slime, lava) to the screen
-		for(var h = 0; h < level.hazards.length; h++) {
+		for (var h = 0; h < level.hazards.length; h++) {
 			new Sprite.Profile(
 				level.hazards[h].type,
 				frames.field,
@@ -730,7 +708,7 @@ var Level = function(l, n) {
 		}
 
 		// Add any shooters
-		for(var s = 0; s < level.shooters.length; s++) {
+		for (var s = 0; s < level.shooters.length; s++) {
 			new Sprite.Profile(
 				level.shooters[s].type,
 				frames.field,
@@ -778,22 +756,14 @@ var Level = function(l, n) {
 
 		// Set up a timed lemon-release event and "remaining lemons" counter
 		var remaining = level.lemons;
-		var releaseLemon = function(x, y) {
-			new Sprite.Profile(
-				"lemon",
-				frames.field,
-				x,
-				y,
-				"e",
-				"normal"
-			);
+		function releaseLemon(x, y) {
+			new Sprite.Profile("lemon", frames.field, x, y, "e", "normal");
 			Sprite.profiles[Sprite.profiles.length - 1].frame.open();
 			Sprite.profiles[Sprite.profiles.length - 1].ini.skill = "lemon";
 			remaining--;
 			frames.counters.remaining.clear();
 			frames.counters.remaining.putmsg((level.lemons - remaining) + "/" + level.lemons);
-			if(nuked)
-				nuke(Sprite.profiles[Sprite.profiles.length - 1]);
+			if (nuked) nuke(Sprite.profiles[Sprite.profiles.length - 1]);
 		}
 		frames.counters.remaining.putmsg(remaining);
 
@@ -801,15 +771,13 @@ var Level = function(l, n) {
 			3000,
 			level.lemons,
 			releaseLemon,
-			[	frames.field.x + level.entrance.x - 1,
-				frames.field.y + level.entrance.y - 1
-			]
+			[frames.field.x + level.entrance.x - 1, frames.field.y + level.entrance.y - 1]
 		);
 		total = level.lemons; // When lost + saved == total, the level is done
 
 		// Set up a timed event to update the clock
 		countDown = level.time + 3; // Don't penalize users for delayed release
-		var tickTock = function() {
+		function tickTock() {
 			countDown--;
 			frames.counters.time.clear();
 			frames.counters.time.putmsg(countDown);
@@ -828,41 +796,35 @@ var Level = function(l, n) {
 	/*	Make the lemons behave according to skillset, or die if necessary.
 		Return a LEVEL_ value to the parent script indicating whether the level
 		is complete (and why) or if it is in progress. */
-	this.cycle = function() {
-
+	this.cycle = function () {
 		/*	Record the index of the first lemon that overlaps with the cursor,
 			if any, for use when assigning skills during this.getcmd.
 			Make the cursor red if there's an overlap, white if not. */
 		cursor.frame.top();
 		var overlaps = Sprite.checkOverlap(cursor);
-		if(overlaps) {
-			for(var o = 0; o < overlaps.length; o++) {
-				if(overlaps[o].ini.type != "lemon")
-					continue;
+		if (overlaps) {
+			for (var o = 0; o < overlaps.length; o++) {
+				if (overlaps[o].ini.type != "lemon") continue;
 				cursor.frame.data[0][0].attr = LIGHTRED;
 				cursor.ini.hoveringOver = overlaps[o].index;
 				break;
 			}
-		} else if(typeof cursor.ini.hoveringOver != "undefined") {
+		} else if (typeof cursor.ini.hoveringOver != "undefined") {
 			cursor.frame.data[0][0].attr = WHITE;
 			delete cursor.ini.hoveringOver;
 		}
 
-		for(var s = 0; s < Sprite.profiles.length; s++) {
+		for (var s = 0; s < Sprite.profiles.length; s++) {
 
-			if(Sprite.profiles[s].ini.type == "shooter") {
+			if (Sprite.profiles[s].ini.type == "shooter") {
 				Sprite.profiles[s].putWeapon();
 				continue;
 			}
 
-			if(	Sprite.profiles[s].ini.type == "projectile"
-				&&
-				Sprite.profiles[s].open
-			) {
+			if (Sprite.profiles[s].ini.type == "projectile" && Sprite.profiles[s].open) {
 				var overlaps = Sprite.checkOverlap(Sprite.profiles[s]);
-				for(var o = 0; o < overlaps.length; o++) {
-					if(overlaps[o].ini.type != "lemon")
-						continue;
+				for (var o = 0; o < overlaps.length; o++) {
+					if (overlaps[o].ini.type != "lemon") continue;
 					overlaps[o].remove();
 					lost++;
 					frames.counters.lostSaved.clear();
@@ -870,17 +832,13 @@ var Level = function(l, n) {
 				}
 			}
 
-			if(Sprite.profiles[s].ini.type != "lemon" || !Sprite.profiles[s].open)
-				continue;
+			if (Sprite.profiles[s].ini.type != "lemon" || !Sprite.profiles[s].open) continue;
 
 			// Remove any lemons that have gone off the screen
-			if(	!Sprite.profiles[s].open
-				||
-				Sprite.profiles[s].y + Sprite.profiles[s].ini.height > frames.field.y + frames.field.height
-				||
-				Sprite.profiles[s].x + Sprite.profiles[s].ini.width <= frames.field.x
-				||
-				Sprite.profiles[s].x >= frames.field.x + frames.field.width
+			if (!Sprite.profiles[s].open
+				|| Sprite.profiles[s].y + Sprite.profiles[s].ini.height > frames.field.y + frames.field.height
+				|| Sprite.profiles[s].x + Sprite.profiles[s].ini.width <= frames.field.x
+				|| Sprite.profiles[s].x >= frames.field.x + frames.field.width
 			) {
 				Sprite.profiles[s].remove();
 				lost++;
@@ -890,52 +848,32 @@ var Level = function(l, n) {
 
 			// Don't stand on top of a door, start a death march if on a hazard
 			var below = Sprite.checkBelow(Sprite.profiles[s]);
-			if(below) {
-				for(var b = 0; b < below.length; b++) {
-					if(	below[b].ini.type == "exit"
-						||
-						below[b].ini.type == "entrance"
-					) {
-						Sprite.profiles[s].moveTo(
-							Sprite.profiles[s].x,
-							Sprite.profiles[s].y + 1
-						);
+			if (below) {
+				for (var b = 0; b < below.length; b++) {
+					if (below[b].ini.type == "exit" || below[b].ini.type == "entrance") {
+						Sprite.profiles[s].moveTo(Sprite.profiles[s].x, Sprite.profiles[s].y + 1);
 						break;
-					} else if(
-						below[b].ini.type == "hazard"
-						&&
-						Sprite.profiles[s].ini.skill != "dying"
-					) {
+					} else if (below[b].ini.type == "hazard" && Sprite.profiles[s].ini.skill != "dying") {
 						Sprite.profiles[s].ini.constantmotion = 0;
 						lost++;
 						frames.counters.lostSaved.clear();
 						frames.counters.lostSaved.putmsg(lost + "/" + saved);
 						Sprite.profiles[s].ini.skill = "dying";
 						colorize(Sprite.profiles[s], COLOUR_DYING);
-						timer.addEvent(
-							1000,
-							false,
-							remove,
-							[Sprite.profiles[s]]
-						);
+						timer.addEvent(1000, false, remove, [Sprite.profiles[s]]);
 						break;
 					}
 				}
-			} else if(
-				Sprite.profiles[s].ini.skill != "digger"
-				&&
-				typeof Sprite.profiles[s].ini.forceDrop == "undefined"
-				&&
-				Sprite.profiles[s].ini.skill != "basher"
+			} else if (
+				Sprite.profiles[s].ini.skill != KEY_DIG
+				&& typeof Sprite.profiles[s].ini.forceDrop == "undefined"
+				&& Sprite.profiles[s].ini.skill != KEY_BASH
 			) {
 				/*	Sprite() moves things horizontally first, then vertically.
 					In certain circumstances (a hole of only the same width as
 					the sprite has just appeared beneath it) we need to force
 					the sprite to drop one cell to initiate a fall.	*/
-				Sprite.profiles[s].moveTo(
-					Sprite.profiles[s].x,
-					Sprite.profiles[s].y + 1
-				);
+				Sprite.profiles[s].moveTo(Sprite.profiles[s].x, Sprite.profiles[s].y + 1);
 				// But we only want to do it once, not every .cycle()
 				Sprite.profiles[s].ini.forceDrop = true;
 				/*	This will be cleared in fallIfNoFloor once the lemon
@@ -943,35 +881,20 @@ var Level = function(l, n) {
 			}
 
 			// Animate the lemon's walk, if applicable
-			if(	((	!Sprite.profiles[s].inFall
-					&&
-					system.timer - Sprite.profiles[s].lastMove > Sprite.profiles[s].ini.speed
-				)
-				||
-				(	Sprite.profiles[s].inFall
-					&&
-					system.timer - Sprite.profiles[s].lastYMove > Sprite.profiles[s].ini.speed	
-				))
-				&&
-				Sprite.profiles[s].ini.gravity == 1
-				&&
-				Sprite.profiles[s].ini.skill != "blocker"
-				&&
-				Sprite.profiles[s].position != "nuked"
-				&&
-				Sprite.profiles[s].ini.skill != "builder"
-				&&
-				Sprite.profiles[s].ini.skill != "digger"
-				&&
-				typeof Sprite.profiles[s].ini.lastDig == "undefined"
+			if (((!Sprite.profiles[s].inFall && system.timer - Sprite.profiles[s].lastMove > Sprite.profiles[s].ini.speed)
+				|| (Sprite.profiles[s].inFall && system.timer - Sprite.profiles[s].lastYMove > Sprite.profiles[s].ini.speed	))
+				&& Sprite.profiles[s].ini.gravity == 1
+				&& Sprite.profiles[s].ini.skill != KEY_BLOCK
+				&& Sprite.profiles[s].position != "nuked"
+				&& Sprite.profiles[s].ini.skill != KEY_BUILD
+				&& Sprite.profiles[s].ini.skill != KEY_DIG
+				&& typeof Sprite.profiles[s].ini.lastDig == "undefined"
 			) {
-				Sprite.profiles[s].changePosition(
-					(Sprite.profiles[s].position == "normal") ? "normal2" : "normal"
-				);
+				Sprite.profiles[s].changePosition((Sprite.profiles[s].position == "normal") ? "normal2" : "normal");
 			}
 
 			// Make the different types of lemons behave as they should
-			switch(Sprite.profiles[s].ini.skill) {
+			switch (Sprite.profiles[s].ini.skill) {
 
 				case "lemon":
 					turnOrClimbIfObstacle(Sprite.profiles[s]);
@@ -979,36 +902,36 @@ var Level = function(l, n) {
 					removeIfAtExit(Sprite.profiles[s]);
 					break;
 
-				case "basher":
+				case KEY_BASH:
 					bashersGonnaBash(Sprite.profiles[s]);
 					fallIfNoFloor(Sprite.profiles[s]);
 					removeIfAtExit(Sprite.profiles[s]);
 					break;
 
-				case "blocker":
+				case KEY_BLOCK:
 					fallIfNoFloor(Sprite.profiles[s]);
 					break;
 
-				case "bomber":
+				case KEY_BOMB:
 					turnOrClimbIfObstacle(Sprite.profiles[s]);
 					fallIfNoFloor(Sprite.profiles[s]);
 					removeIfAtExit(Sprite.profiles[s]);
 					break;
 
-				case "builder":
+				case KEY_BUILD:
 					turnOrClimbIfObstacle(Sprite.profiles[s]);
 					buildersGonnaBuild(Sprite.profiles[s]);
 					fallIfNoFloor(Sprite.profiles[s]);
 					removeIfAtExit(Sprite.profiles[s]);
 					break;
 
-				case "climber":
+				case KEY_CLIMB:
 					climbIfObstacle(Sprite.profiles[s]);
 					fallIfNoFloor(Sprite.profiles[s]);
 					removeIfAtExit(Sprite.profiles[s]);
 					break;
 
-				case "digger":
+				case KEY_DIG:
 					diggersGonnaDig(Sprite.profiles[s]);
 					fallIfNoFloor(Sprite.profiles[s]);
 					break;
@@ -1025,15 +948,14 @@ var Level = function(l, n) {
 		Sprite.cycle();
 
 		// Tell the parent script how to proceed
-		if(countDown <= 0) {
-			if(saved < (total / 2))
-				return LEVEL_TIME;
+		if (countDown <= 0) {
+			if (saved < (total / 2)) return LEVEL_TIME;
 			this.score = (saved * 100);
 			return LEVEL_NEXT;
-		} else if(lost + saved == total && saved >= (total / 2)) {
+		} else if (lost + saved == total && saved >= (total / 2)) {
 			this.score = (saved * 100) + (countDown * 10);
 			return LEVEL_NEXT;
-		} else if(lost + saved == total && saved < (total / 2)) {
+		} else if (lost + saved == total && saved < (total / 2)) {
 			return LEVEL_DEAD;
 		} else {
 			return LEVEL_CONTINUE;
@@ -1042,11 +964,69 @@ var Level = function(l, n) {
 	}
 
 	// Do what the user asked us to do, if it's valid
-	this.getcmd = function(userInput) {
+	this.getcmd = function (userInput) {
 
 		var ret = true;
 
-		switch(userInput.toUpperCase()) {
+		if (userInput.mouse !== null) {
+			// If it wasn't a left-click
+			if (userInput.mouse.button != 0) return ret;
+			// If they clicked inside the statusbar region ...
+			if (userInput.mouse.y >= frames.statusBar.y && userInput.mouse.y <= frames.statusBar.y + 1) {
+				if (userInput.mouse.x < frames.statusBar.x + 12) {
+					if (userInput.mouse.y == frames.statusBar.y) {
+						// Bash
+						if (quotas[KEY_BASH] > 0) currentSkill = KEY_BASH;
+					} else {
+						// Block
+						if (quotas[KEY_BLOCK] > 0) currentSkill = KEY_BLOCK;
+					}
+				} else if (userInput.mouse.x <= frames.statusBar.x + 26) {
+					if (userInput.mouse.y == frames.statusBar.y) {
+						// Bomb
+						if (quotas[KEY_BOMB] > 0) currentSkill = KEY_BOMB;
+					} else {
+						// Build
+						if (quotas[KEY_BUILD] > 0) currentSkill = KEY_BUILD;
+					}
+				} else if (userInput.mouse.x <= frames.statusBar.x + 40) {
+					if (userInput.mouse.y == frames.statusBar.y) {
+						if (quotas[KEY_CLIMB] > 0) currentSkill = KEY_CLIMB;
+					} else {
+						if (quotas[KEY_DIG] > 0) currentSkill = KEY_DIG;
+					}
+				} else if (userInput.mouse.x <= frames.statusBar.x + 48) {
+					if (userInput.mouse.y == frames.statusBar.y) {
+						// Sure, let's just paste some crap
+						for (var s = 0; s < Sprite.profiles.length; s++) {
+							if (Sprite.profiles[s].ini.type != "lemon" || !Sprite.profiles[s].open) continue;
+							nuke(Sprite.profiles[s]);
+						}
+						nuked = true;
+					} else {
+						state = STATE_PAUSE;
+					}
+				} else if (userInput.mouse.x <= frames.statusBar.x + 48) {
+					if (userInput.mouse.y == frames.statusBar.y) {
+						state = STATE_HELP;
+					} else {
+						return false;
+					}
+				}
+			} else if (
+				userInput.mouse.y >= frames.field.y
+				&& userInput.mouse.y < frames.field.y + frames.field.height
+				&& userInput.mouse.x >= frames.field.x
+				&& userInput.mouse.x < frames.field.x + frames.field.width
+			) {
+				cursor.moveTo(userInput.mouse.x, userInput.mouse.y);
+				this.cycle();
+				if (currentSkill != null) return this.getcmd({ key: '\r', mouse: null });
+			}
+			return ret;
+		}
+
+		switch (userInput.key.toUpperCase()) {
 
 			case "":
 				break;
@@ -1069,139 +1049,89 @@ var Level = function(l, n) {
 			// Cursor movement
 			case "8":
 			case KEY_UP:
-				if(cursor.y > frames.field.y)
-					cursor.moveTo(cursor.x, cursor.y - 1);
+				if (cursor.y > frames.field.y) cursor.moveTo(cursor.x, cursor.y - 1);
 				break;
 
 			case "2":
 			case KEY_DOWN:
-				if(cursor.y < frames.field.y + frames.field.height - 1)
-					cursor.moveTo(cursor.x, cursor.y + 1);
+				if (cursor.y < frames.field.y + frames.field.height - 1) cursor.moveTo(cursor.x, cursor.y + 1);
 				break;
 
 			case "4":
 			case KEY_LEFT:
-				if(cursor.x > frames.field.x)
-					cursor.moveTo(cursor.x - 1, cursor.y);
+				if (cursor.x > frames.field.x) cursor.moveTo(cursor.x - 1, cursor.y);
 				break;
 
 			case "6":
 			case KEY_RIGHT:
-				if(cursor.x < frames.field.x + frames.field.width - 1)
-					cursor.moveTo(cursor.x + 1, cursor. y);
+				if (cursor.x < frames.field.x + frames.field.width - 1) cursor.moveTo(cursor.x + 1, cursor. y);
 				break;
 
 			case "7":
-				if(cursor.y > frames.field.y && cursor.x > frames.field.x)
-					cursor.moveTo(cursor.x - 1, cursor.y - 1);
+				if (cursor.y > frames.field.y && cursor.x > frames.field.x) cursor.moveTo(cursor.x - 1, cursor.y - 1);
 				break;
 
 			case "9":
-				if(cursor.y > frames.field.y && cursor.x < frames.field.x + frames.field.width - 1)
-					cursor.moveTo(cursor.x + 1, cursor.y - 1);
+				if (cursor.y > frames.field.y && cursor.x < frames.field.x + frames.field.width - 1) cursor.moveTo(cursor.x + 1, cursor.y - 1);
 				break;
 
 			case "1":
-				if(cursor.y < frames.field.y + frames.field.height - 1 && cursor.x > frames.field.x)
-					cursor.moveTo(cursor.x - 1, cursor.y + 1);
+				if (cursor.y < frames.field.y + frames.field.height - 1 && cursor.x > frames.field.x) cursor.moveTo(cursor.x - 1, cursor.y + 1);
 				break;
 
 			case "3":
-				if(cursor.y < frames.field.y + frames.field.height - 1 && cursor.x < frames.field.x + frames.field.width - 1)
-					cursor.moveTo(cursor.x + 1, cursor.y + 1);
+				if (cursor.y < frames.field.y + frames.field.height - 1 && cursor.x < frames.field.x + frames.field.width - 1) cursor.moveTo(cursor.x + 1, cursor.y + 1);
 				break;
 
-			// Nuke the h'wales
 			case "N":
-				for(var s = 0; s < Sprite.profiles.length; s++) {
-					if(Sprite.profiles[s].ini.type != "lemon" || !Sprite.profiles[s].open)
-						continue;
+				for (var s = 0; s < Sprite.profiles.length; s++) {
+					if (Sprite.profiles[s].ini.type != "lemon" || !Sprite.profiles[s].open) continue;
 					nuke(Sprite.profiles[s]);
 				}
 				nuked = true;
 				break;
 
-			// Basher
 			case KEY_BASH:
-				if(typeof cursor.ini.hoveringOver == "undefined")
-					break;
-				if(quotas.basher < 1)
-					break;
-				quotas.basher--;
-				frames.counters.basher.clear();
-				frames.counters.basher.putmsg(quotas.basher);
-				Sprite.profiles[cursor.ini.hoveringOver].ini.skill = "basher";
-				colorize(Sprite.profiles[cursor.ini.hoveringOver], COLOUR_BASHER);
-				break;
-
-			// Blocker
 			case KEY_BLOCK:
-				if(typeof cursor.ini.hoveringOver == "undefined")
-					break;
-				if(quotas.blocker < 1)
-					break;
-				quotas.blocker--;
-				frames.counters.blocker.clear();
-				frames.counters.blocker.putmsg(quotas.blocker);
-				Sprite.profiles[cursor.ini.hoveringOver].ini.skill = "blocker";
-				Sprite.profiles[cursor.ini.hoveringOver].ini.constantmotion = 0;
-				Sprite.profiles[cursor.ini.hoveringOver].changePosition("fall");
-				colorize(Sprite.profiles[cursor.ini.hoveringOver], COLOUR_BLOCKER);
-				timer.addEvent(1000, true, tapFoot, [Sprite.profiles[cursor.ini.hoveringOver]]);
-				break;
-
-			// Bomber
 			case KEY_BOMB:
-				if(typeof cursor.ini.hoveringOver == "undefined")
-					break;
-				if(quotas.bomber < 1)
-					break;
-				quotas.bomber--;
-				frames.counters.bomber.clear();
-				frames.counters.bomber.putmsg(quotas.bomber);
-				Sprite.profiles[cursor.ini.hoveringOver].ini.skill = "bomber";
-				nuke(Sprite.profiles[cursor.ini.hoveringOver]);
-				break;
-
-			// Builder
 			case KEY_BUILD:
-				if(typeof cursor.ini.hoveringOver == "undefined")
-					break;
-				if(quotas.builder < 1)
-					break;
-				quotas.builder--;
-				frames.counters.builder.clear();
-				frames.counters.builder.putmsg(quotas.builder);
-				Sprite.profiles[cursor.ini.hoveringOver].ini.skill = "builder";
-				colorize(Sprite.profiles[cursor.ini.hoveringOver], COLOUR_BUILDER);
-				break;
-
-			// Climber
 			case KEY_CLIMB:
-				if(typeof cursor.ini.hoveringOver == "undefined")
-					break;
-				if(quotas.climber < 1)
-					break;
-				quotas.climber--;
-				frames.counters.climber.clear();
-				frames.counters.climber.putmsg(quotas.climber);
-				Sprite.profiles[cursor.ini.hoveringOver].ini.skill = "climber";
-				colorize(Sprite.profiles[cursor.ini.hoveringOver], COLOUR_CLIMBER);
+			case KEY_DIG:
+				if (currentSkill !== null) {
+					frames.labels[currentSkill].data[0] = frames.labels[currentSkill].data[0].map(function (e) {
+						if (typeof e != 'object') return e;
+						e.attr &=~ BG_LIGHTGRAY;
+						e.attr |= COLOUR_STATUSBAR_BG;
+						return e;
+					});
+					frames.labels[currentSkill].invalidate();		
+				}
+				currentSkill = userInput.key.toUpperCase();
+				frames.labels[currentSkill].data[0] = frames.labels[currentSkill].data[0].map(function (e) {
+					if (typeof e != 'object') return e;
+					e.attr &=~ COLOUR_STATUSBAR_BG;
+					e.attr |= BG_LIGHTGRAY;
+					return e;
+				});
+				frames.labels[currentSkill].invalidate();
 				break;
 
-			// Digger
-			case KEY_DIG:
-				if(typeof cursor.ini.hoveringOver == "undefined")
-					break;
-				if(quotas.digger < 1)
-					break;
-				quotas.digger--;
-				frames.counters.digger.clear();
-				frames.counters.digger.putmsg(quotas.digger);
-				Sprite.profiles[cursor.ini.hoveringOver].ini.skill = "digger";
-				Sprite.profiles[cursor.ini.hoveringOver].ini.constantmotion = 0;
-				Sprite.profiles[cursor.ini.hoveringOver].changePosition("fall");
-				colorize(Sprite.profiles[cursor.ini.hoveringOver], COLOUR_DIGGER);
+			case "\r":
+			case " ":
+				if (typeof cursor.ini.hoveringOver == "undefined") break;
+				if (quotas[currentSkill] < 1) break;
+				quotas[currentSkill]--;
+				frames.counters[currentSkill].clear();
+				frames.counters[currentSkill].putmsg(quotas[currentSkill]);
+				Sprite.profiles[cursor.ini.hoveringOver].ini.skill = currentSkill;
+				colorize(Sprite.profiles[cursor.ini.hoveringOver], COLOUR[currentSkill]);
+				if (currentSkill == KEY_BLOCK || currentSkill == KEY_DIG) {
+					Sprite.profiles[cursor.ini.hoveringOver].ini.constantmotion = 0;
+					Sprite.profiles[cursor.ini.hoveringOver].changePosition("fall");
+					if (currentSkill == KEY_BLOCK) timer.addEvent(1000, true, tapFoot, [Sprite.profiles[cursor.ini.hoveringOver]]);
+				} else if (currentSkill == KEY_BOMB) {
+					nuke(Sprite.profiles[cursor.ini.hoveringOver]);
+				}
 				break;
 
 			default:
@@ -1212,43 +1142,27 @@ var Level = function(l, n) {
 
 	}
 
-	/*	Halt or resume all timed events.
-		This doesn't actually block - that should be done in the main loop.
-		If no argument is specified, events are removed from the timer and an
-		array of these events is returned.
-		If an argument is specified, it is assumed to be an array of events,
-		and they are added back into the timer. */
-	this.pause = function(events) {
-		if(typeof events == "undefined") {
+	this.pause = function (events) {
+		if (typeof events == "undefined") {
 			var events = timer.events;
 			timer.events = [];
 			return events;
 		} else {
-			for(var e = 0; e < events.length; e++) {
-				timer.addEvent(
-					events[e].interval,
-					events[e].repeat,
-					events[e].action,
-					events[e].arguments,
-					events[e].context
-				);
+			for (var e = 0; e < events.length; e++) {
+				timer.addEvent(events[e].interval, events[e].repeat, events[e].action, events[e].arguments, events[e].context);
 			}
 		}
 	}
 
 	// Reset the Sprite globals, clean up the display
-	this.close = function() {
-		
+	this.close = function () {
 		cursor.remove();
 		Sprite.platforms = [];
-
-		for(var s = 0; s < Sprite.profiles.length; s++) {
+		for (var s = 0; s < Sprite.profiles.length; s++) {
 			Sprite.profiles[s].remove();
 		}
 		Sprite.profiles = [];
-
 		frames.game.delete();
-
 	}
 
 	loadLevel(l, frame);
-- 
GitLab