Index: code/menuui/mainhallmenu.cpp
===================================================================
--- code/menuui/mainhallmenu.cpp	(revision 10896)
+++ code/menuui/mainhallmenu.cpp	(working copy)
@@ -33,6 +33,7 @@
 #include "menuui/fishtank.h"
 #include "mission/missioncampaign.h"
 #include "parse/parselo.h"
+#include "parse/scripting.h"
 #include "network/multiui.h"
 #include "network/multiutil.h"
 #include "network/multi_voice.h"
@@ -151,8 +152,7 @@
 // ----------------------------------------------------------------------------
 // SNAZZY MENU stuff
 //
-#define NUM_MAIN_HALL_REGIONS 10
-#define NUM_MAIN_HALL_MOUSE_REGIONS 6
+#define NUM_MAIN_HALL_MAX_REGIONS 20
 
 // region mask #'s (identifiers)
 #define EXIT_REGION				0
@@ -165,9 +165,32 @@
 #define LOAD_MISSION_REGION		11
 #define QUICK_START_REGION		12
 #define SKILL_LEVEL_REGION		13
+#define SCRIPT_REGION			14
+#define START_REGION			15
 
+struct main_hall_region_info {
+	int mask;
+	char *name;
+};
+
+main_hall_region_info Main_hall_region_map[] = {
+	{ EXIT_REGION, "Exit" },
+	{ BARRACKS_REGION, "Barracks" },
+	{ READY_ROOM_REGION, "Readyroom" },
+	{ TECH_ROOM_REGION, "Techroom" },
+	{ OPTIONS_REGION, "Options" },
+	{ CAMPAIGN_ROOM_REGION, "Campaigns" },
+	{ MULTIPLAYER_REGION, "Multiplayer" },
+	{ LOAD_MISSION_REGION, "Load Mission" },
+	{ QUICK_START_REGION, "Quickstart" },
+	{ SKILL_LEVEL_REGION, "Skilllevel" },
+	{ SCRIPT_REGION, "Script" },
+	{ START_REGION, "Start" },
+	{ -1, NULL }
+};
+
 // all the menu regions in the main hall
-MENU_REGION Main_hall_region[NUM_MAIN_HALL_REGIONS];
+MENU_REGION Main_hall_region[NUM_MAIN_HALL_MAX_REGIONS];
 
 // # of regions (options) on this screen. parsed from a table
 int Main_hall_num_options;
@@ -395,7 +418,6 @@
 	}
 
 	int idx;
-	char temp[100], whee[100];
 	SCP_string main_hall_to_load;
 
 	// reparse the table here if the relevant cmdline flag is set
@@ -423,20 +445,24 @@
 
 	// create the snazzy interface and load up the info from the table
 	snazzy_menu_init();
-	read_menu_tbl(NOX("MAIN HALL"), temp, whee, Main_hall_region, &Main_hall_num_options, 0);
-
+	
 	// assign the proper main hall data
 	Assert(main_hall_get_pointer(main_hall_to_load) != NULL);
 	Main_hall = main_hall_get_pointer(main_hall_to_load);
 
-	// tooltip strings
-	Main_hall->region_descript.at(0) = XSTR( "Exit FreeSpace 2", 353);
-	Main_hall->region_descript.at(1) = XSTR( "Barracks - Manage your FreeSpace 2 pilots", 354);
-	Main_hall->region_descript.at(2) = XSTR( "Ready room - Start or continue a campaign", 355);
-	Main_hall->region_descript.at(3) = XSTR( "Tech room - View specifications of FreeSpace 2 ships and weaponry", 356);
-	Main_hall->region_descript.at(4) = XSTR( "Options - Change your FreeSpace 2 options", 357);
-	Main_hall->region_descript.at(5) = XSTR( "Campaign Room - View all available campaigns", 358);
-	Main_hall->region_descript.at(6) = XSTR( "Multiplayer - Start or join a multiplayer game", 359);
+	// check if we have to change the ready room's description
+	if (Player->flags & PLAYER_FLAGS_IS_MULTI) {
+		if(Main_hall->default_readyroom) {
+			Main_hall->regions[2].description = XSTR( "Multiplayer - Start or join a multiplayer game", 359);
+		}
+	}
+	
+	// Read the menu regions from mainhall.tbl
+	SCP_vector<main_hall_region>::iterator it;
+	for (it = Main_hall->regions.begin(); Main_hall->regions.end() != it; ++it) {
+		snazzy_menu_add_region(Main_hall_region + (it - Main_hall->regions.begin()), it->description.c_str(), it->mask, 0, -1);
+	}
+	Main_hall_num_options = Main_hall->regions.size();
 
 	// init tooltip shader						// nearly black
 	gr_create_shader(&Main_hall_tooltip_shader, 5, 5, 5, 168);
@@ -637,7 +663,8 @@
  */
 void main_hall_do(float frametime)
 {
-	int code, key, snazzy_action;
+	int code, key, snazzy_action, region_action = -1;
+	SCP_vector<main_hall_region>::iterator it;
 
 	// set the screen scale to the main hall's dimensions
 	gr_set_screen_scale(Main_hall_bitmap_w, Main_hall_bitmap_h, Main_hall->zoom_area_width, Main_hall->zoom_area_height);
@@ -692,11 +719,38 @@
 	// do any processing based upon what happened to the snazzy menu
 	switch (snazzy_action) {
 		case SNAZZY_OVER:
-			main_hall_handle_mouse_location(code);
+			for (it = Main_hall->regions.begin(); Main_hall->regions.end() != it; ++it) {
+				if (it->mask == code) {
+					main_hall_handle_mouse_location(it - Main_hall->regions.begin());
+					break;
+				}
+			}
+			
 			break;
 
 		case SNAZZY_CLICKED:
-			switch (code) {
+			if (code == ESC_PRESSED) {
+				region_action = ESC_PRESSED;
+			} else {
+				for (it = Main_hall->regions.begin(); Main_hall->regions.end() != it; ++it) {
+					if (it->mask == code) {
+						region_action = it->action;
+						break;
+					}
+				}
+				
+				if (region_action == -1) {
+					Error(LOCATION, "Region %d doesn't have an action!", code);
+				} else if (region_action == START_REGION) {
+					if (Player->flags & PLAYER_FLAGS_IS_MULTI) {
+						region_action = MULTIPLAYER_REGION;
+					} else {
+						region_action = READY_ROOM_REGION;
+					}
+				}
+			}
+			
+			switch (region_action) {
 				// clicked on the exit region
 				case EXIT_REGION:
 					gamesnd_play_iface(SND_IFACE_MOUSE_CLICK);
@@ -705,17 +759,16 @@
 
 				// clicked on the readyroom region
 				case READY_ROOM_REGION:
-					if (Player->flags & PLAYER_FLAGS_IS_MULTI) {
-						gamesnd_play_iface(SND_IFACE_MOUSE_CLICK);
-						main_hall_do_multi_ready();
+					// Make sure we aren't in multi mode.
+					Player->flags = Player->flags ^ PLAYER_FLAGS_IS_MULTI;
+					Game_mode = GM_NORMAL;
+					
+					if (strlen(Main_hall_campaign_cheat)) {
+						gameseq_post_event(GS_EVENT_CAMPAIGN_CHEAT);
 					} else {
-						if (strlen(Main_hall_campaign_cheat)) {
-							gameseq_post_event(GS_EVENT_CAMPAIGN_CHEAT);
-						} else {
-							gameseq_post_event(GS_EVENT_NEW_CAMPAIGN);
-						}
-						gamesnd_play_iface(SND_IFACE_MOUSE_CLICK);
+						gameseq_post_event(GS_EVENT_NEW_CAMPAIGN);
 					}
+					gamesnd_play_iface(SND_IFACE_MOUSE_CLICK);
 					break;
 
 				// clicked on the tech room region
@@ -732,23 +785,20 @@
 
 				// clicked on the campaign toom region
 				case CAMPAIGN_ROOM_REGION:
-					if (Player->flags & PLAYER_FLAGS_IS_MULTI) {
-						gamesnd_play_iface(SND_IFACE_MOUSE_CLICK);
-						main_hall_set_notify_string(XSTR( "Campaign Room not valid for multiplayer pilots", 366));
-					} else {
-						gamesnd_play_iface(SND_IFACE_MOUSE_CLICK);
-						gameseq_post_event(GS_EVENT_CAMPAIGN_ROOM);
-					}
+					gamesnd_play_iface(SND_IFACE_MOUSE_CLICK);
+					gameseq_post_event(GS_EVENT_CAMPAIGN_ROOM);
 					break;
 
 				// clicked on the multiplayer region
 				case MULTIPLAYER_REGION:
-					if (Player->flags & PLAYER_FLAGS_IS_MULTI) {
-						// NOTE : this isn't a great thing to be calling this anymore. But we'll leave it for now
-						gameseq_post_event(GS_EVENT_MULTI_JOIN_GAME);
-					} else {
-						main_hall_set_notify_string(XSTR( "Not a valid multiplayer pilot!!", 367));
-					}
+					// Make sure we are in multi mode.
+					Player->flags |= PLAYER_FLAGS_IS_MULTI;
+					Game_mode = GM_MULTIPLAYER;
+					
+					main_hall_do_multi_ready();
+					
+					// NOTE : this isn't a great thing to be calling this anymore. But we'll leave it for now
+					gameseq_post_event(GS_EVENT_MULTI_JOIN_GAME);
 					break;
 
 				// load mission key was pressed
@@ -758,20 +808,16 @@
 				// quick start a game region
 				case QUICK_START_REGION:
 			#if !defined(NDEBUG)
-					if (Player->flags & PLAYER_FLAGS_IS_MULTI) {
-						main_hall_set_notify_string(XSTR( "Quick Start not valid for multiplayer pilots", 369));
+					if (Num_recent_missions > 0) {
+						strcpy_s(Game_current_mission_filename, Recent_missions[0]);
 					} else {
-						if (Num_recent_missions > 0) {
-							strcpy_s(Game_current_mission_filename, Recent_missions[0]);
-						} else {
-							if (mission_load_up_campaign()) {
-								main_hall_set_notify_string(XSTR( "Campaign file is currently unavailable", 1606));
-							}
-							strcpy_s(Game_current_mission_filename, Campaign.missions[0].name);
+						if (mission_load_up_campaign()) {
+							main_hall_set_notify_string(XSTR( "Campaign file is currently unavailable", 1606));
 						}
-						Campaign.current_mission = -1;
-						gameseq_post_event(GS_EVENT_START_GAME_QUICK);
+						strcpy_s(Game_current_mission_filename, Campaign.missions[0].name);
 					}
+					Campaign.current_mission = -1;
+					gameseq_post_event(GS_EVENT_START_GAME_QUICK);
 			#endif
 					break;
 
@@ -799,11 +845,17 @@
 						help_overlay_set_state(Main_hall_overlay_id,gr_screen.res,0);
 					}
 					break;
+				
+				// custom action
+				case SCRIPT_REGION:
+					const char *lua = it->lua_action.c_str();
+					Script_system.EvalString(lua, NULL, NULL, lua);
+					break;
 			} // END switch (code)
 
 			// if the escape key wasn't pressed handle any mouse position related events
 			if (code != ESC_PRESSED) {
-				main_hall_handle_mouse_location(code);
+				main_hall_handle_mouse_location((region_action == -1 ? -1 : it - Main_hall->regions.begin()));
 			}
 			break;
 
@@ -1211,8 +1263,8 @@
 	if (Main_hall_frame_skip) {
 		return;
 	}
-
-	if (cur_region > NUM_MAIN_HALL_MOUSE_REGIONS) {
+	
+	if (cur_region > (int)Main_hall->regions.size() - 1) {
 		// MWA -- inserted return since Int3() was tripped when hitting L from main menu.
 		return;
 	}
@@ -1262,7 +1314,7 @@
 	}
 
 	// don't do anything if there are no animations to play
-	else if(Main_hall_door_anim.size() == 0)
+	else if(region > (int) Main_hall_door_anim.size() - 1)
 	{
 		return;
 	}
@@ -1303,7 +1355,7 @@
 	}
 
 	// don't do anything if there are no animations to play
-	else if(Main_hall_door_anim.size() == 0)
+	else if(region > (int) Main_hall_door_anim.size() - 1)
 	{
 		return;
 	}
@@ -1351,7 +1403,7 @@
 	if (!Main_hall_right_click) {
 		if (mouse_down(MOUSE_RIGHT_BUTTON)) {
 			// cycle through the available regions
-			if (Main_hall_last_clicked_region == NUM_MAIN_HALL_MOUSE_REGIONS - 1) {
+			if (Main_hall_last_clicked_region == (int) Main_hall_door_anim.size() - 1) {
 				new_region = 0;
 			} else {
 				new_region = Main_hall_last_clicked_region + 1;
@@ -1579,7 +1631,7 @@
  */
 void main_hall_maybe_blit_tooltips()
 {
-	int w, h, text_index;
+	int w, h;
 
 	// if we're over no region - don't blit anything
 	if (Main_hall_mouse_region < 0) {
@@ -1586,24 +1638,18 @@
 		return;
 	}
 
-	// get the index of the proper text to be using
-	if (Main_hall_mouse_region == READY_ROOM_REGION) {
-		// if this is a multiplayer pilot, the ready room region becomes the multiplayer region
-		if (Player->flags & PLAYER_FLAGS_IS_MULTI){
-			text_index = NUM_REGIONS - 1;
-		} else {
-			text_index = READY_ROOM_REGION;
-		}
-	} else {
-		text_index = Main_hall_mouse_region;
+	if (Main_hall_mouse_region >= (int) Main_hall->regions.size()) {
+		Error(LOCATION, "Missing region description for index %d!\n", Main_hall_mouse_region);
 	}
 
 	// set the color and blit the string
 	if (!help_overlay_active(Main_hall_overlay_id)) {
+		const char* desc = Main_hall->regions[Main_hall_mouse_region].description.c_str();
+		
 		int old_font = gr_get_current_fontnum();
 		gr_set_font(Main_hall->font);
 		// get the width of the string
-		gr_get_string_size(&w, &h, Main_hall->region_descript.at(text_index));
+		gr_get_string_size(&w, &h, desc);
 		int text_y;
 		if (Main_hall->region_yval == -1) {
 			text_y = gr_screen.max_h_unscaled - ((gr_screen.max_h_unscaled - gr_screen.max_h_unscaled_zoomed) / 2) - Main_hall->tooltip_padding - h;
@@ -1611,12 +1657,12 @@
 			text_y = Main_hall->region_yval;
 		}
 		int shader_y = text_y - (Main_hall->tooltip_padding);	// subtract more to pull higher
-
+		
 		gr_set_shader(&Main_hall_tooltip_shader);
 		gr_shade(0, shader_y, gr_screen.max_w_unscaled, (gr_screen.max_h_unscaled - shader_y), GR_RESIZE_MENU);
 
 		gr_set_color_fast(&Color_bright_white);
-		gr_string((gr_screen.max_w_unscaled - w)/2, text_y, Main_hall->region_descript.at(text_index), GR_RESIZE_MENU);
+		gr_string((gr_screen.max_w_unscaled - w)/2, text_y, desc, GR_RESIZE_MENU);
 
 		gr_set_font(old_font);
 	}
@@ -1876,7 +1922,6 @@
 		m.door_anim_coords.clear();
 		m.door_sounds.clear();
 		m.door_sound_pan.clear();
-		m.region_descript.clear();
 	}
 
 	SCP_vector<int> temp;
@@ -1901,12 +1946,29 @@
 		// door_sound_pan
 		m.door_sound_pan.push_back(0.0f);
 	}
+}
 
-	// region_descript
-	for (idx = 0; idx < NUM_REGIONS; idx++) {
-		m.region_descript.push_back(NULL);
+void region_info_init(main_hall_defines &m)
+{
+	if (Cmdline_reparse_mainhall) {
+		m.regions.clear();
 	}
-
+	
+	main_hall_region defaults[] = {
+		{0, XSTR( "Exit FreeSpace 2", 353), EXIT_REGION, ""},
+		{1, XSTR( "Barracks - Manage your FreeSpace 2 pilots", 354), BARRACKS_REGION, ""},
+		{2, XSTR( "Ready room - Start or continue a campaign", 355), START_REGION, ""},
+		{3, XSTR( "Tech room - View specifications of FreeSpace 2 ships and weaponry", 356), TECH_ROOM_REGION, ""},
+		{4, XSTR( "Options - Change your FreeSpace 2 options", 357), OPTIONS_REGION, ""},
+		{5, XSTR( "Campaign Room - View all available campaigns", 358), CAMPAIGN_ROOM_REGION, ""}
+	};
+	
+	for (int idx = 0; idx < 6; idx++) {
+		m.regions.push_back(defaults[idx]);
+	}
+	
+	// XSTR( "Multiplayer - Start or join a multiplayer game", 359)
+	m.default_readyroom = true;
 }
 
 /**
@@ -1935,6 +1997,7 @@
 	int num_resolutions = 2;
 	unsigned int count;
 	char temp_string[MAX_FILENAME_LEN];
+	SCP_string temp_scp_string;
 
 	if ((rval = setjmp(parse_abort)) != 0) {
 		mprintf(("TABLES: Unable to parse '%s'!  Error code = %i.\n", filename, rval));
@@ -2165,7 +2228,75 @@
 					m->misc_anim_sound_flag.at(idx).push_back(0);
 				}
 			}
+			
+			region_info_init(*m);
+			
+			int mask;
+			for (idx = 0; optional_string("+Door mask value:"); idx++) {
+				// door mask
+				stuff_string(temp_string, F_RAW, MAX_FILENAME_LEN);
+				
+				mask = (int) strtol(temp_string, NULL, 0);
+				mask = 255 - mask;
+				
+				if(idx >= (int) m->regions.size()) {
+					m->regions.resize(idx + 1);
+				}
+				m->regions[idx].mask = mask;
+			}
+			
+			for (idx = 0; optional_string("+Door action:"); idx++) {
+				// door action
+				
+				if(idx >= (int) m->regions.size()) {
+					m->regions.resize(idx + 1);
+				}
+				
+				if (optional_string("Script")) {
+					m->regions[idx].action = SCRIPT_REGION;
+					stuff_string(m->regions[idx].lua_action, F_RAW);
+				} else {
+					stuff_string(temp_scp_string, F_RAW);
+					
+					int action = -1;
+					for (int i = 0; Main_hall_region_map[i].name != NULL; i++) {
+						if (temp_scp_string == Main_hall_region_map[i].name) {
+							action = Main_hall_region_map[i].mask;
+							break;
+						}
+					}
+					
+					if (action == -1) {
+						SCP_string err_msg = "";
+						for (int i = 0; Main_hall_region_map[i].name != NULL; i++) {
+							err_msg += ", ";
+							err_msg += Main_hall_region_map[i].name;
+						}
+						
+						Error(LOCATION, "Unkown Door Region '%s'! Expected one of: %s", temp_scp_string.c_str(), err_msg.substr(2).c_str());
+					}
+					
+					m->regions[idx].action = action;
+				}
+			}
 
+			for (idx = 0; optional_string("+Door description:"); idx++) {
+				// region description (tooltip)
+				stuff_string(temp_scp_string, F_MESSAGE);
+
+				if (temp_scp_string != "default") {
+					if(idx >= (int) m->regions.size()) {
+						m->regions.resize(idx + 1);
+					}
+					
+					m->regions[idx].description = temp_scp_string;
+					
+					if (idx == 2) {
+						m->default_readyroom = false;
+					}
+				}
+			}
+
 			// door animations
 			required_string("+Num Door Animations:");
 			stuff_int(&m->num_door_animations);
Index: code/menuui/mainhallmenu.h
===================================================================
--- code/menuui/mainhallmenu.h	(revision 10896)
+++ code/menuui/mainhallmenu.h	(working copy)
@@ -16,6 +16,13 @@
 // CommanderDJ - this is now dynamic
 // #define MAIN_HALLS_MAX			10
 
+typedef struct main_hall_region {
+	int mask;
+	SCP_string description;
+	int action;
+	SCP_string lua_action;
+} main_hall_region;
+
 typedef struct main_hall_defines {
 	// mainhall name identifier
 	SCP_string name;
@@ -115,8 +122,10 @@
 	// font used for the tooltips, version number, etc.
 	int font;
 
-	// text (tooltip) description
-	SCP_vector<const char*> region_descript;
+	// action
+	SCP_vector<main_hall_region> regions;
+	
+	bool default_readyroom;
 
 	// num pixels shader is above/below tooltip text
 	int tooltip_padding;
Index: code/menuui/snazzyui.cpp
===================================================================
--- code/menuui/snazzyui.cpp	(revision 10896)
+++ code/menuui/snazzyui.cpp	(working copy)
@@ -160,7 +160,7 @@
 //
 //
 
-void snazzy_menu_add_region(MENU_REGION* region, char* text, int mask, int key, int click_sound)
+void snazzy_menu_add_region(MENU_REGION* region, const char* text, int mask, int key, int click_sound)
 {
 	region->mask = mask;
 	region->key = key;
Index: code/menuui/snazzyui.h
===================================================================
--- code/menuui/snazzyui.h	(revision 10896)
+++ code/menuui/snazzyui.h	(working copy)
@@ -30,7 +30,7 @@
 
 int snazzy_menu_do(ubyte *data, int mask_w, int mask_h, int num_regions, MENU_REGION *regions, int *action, int poll_key = 1, int *key = NULL);
 void read_menu_tbl(char *menu_name, char *bkg_filename, char *mask_filename, MENU_REGION *regions, int* num_regions, int play_sound=1);
-void snazzy_menu_add_region(MENU_REGION *region, char* text, int mask, int key, int click_sound = -1);
+void snazzy_menu_add_region(MENU_REGION *region, const char* text, int mask, int key, int click_sound = -1);
 
 void snazzy_menu_init();		// Call the first time a snazzy menu is inited
 void snazzy_menu_close();
Index: code/parse/scripting.cpp
===================================================================
--- code/parse/scripting.cpp	(revision 10896)
+++ code/parse/scripting.cpp	(working copy)
@@ -1069,7 +1069,7 @@
 	return 1;
 }
 
-bool script_state::EvalString(char* string, char *format, void *rtn, char *debug_str)
+bool script_state::EvalString(const char* string, char *format, void *rtn, const char *debug_str)
 {
 	char lastchar = string[strlen(string)-1];
 
Index: code/parse/scripting.h
===================================================================
--- code/parse/scripting.h	(revision 10896)
+++ code/parse/scripting.h	(working copy)
@@ -190,7 +190,7 @@
 	void RemHookVars(unsigned int num, ...);
 
 	//***Hook creation functions
-	bool EvalString(char* string, char *format=NULL, void *rtn=NULL, char *debug_str=NULL);
+	bool EvalString(const char* string, char *format=NULL, void *rtn=NULL, const char *debug_str=NULL);
 	void ParseChunk(script_hook *dest, char* debug_str=NULL);
 	bool ParseCondition(const char *filename="<Unknown>");
 
