summary refs log tree commit diff
path: root/src/Menus.c
diff options
context:
space:
mode:
authorWlodekM <[email protected]>2024-06-16 10:35:45 +0300
committerWlodekM <[email protected]>2024-06-16 10:35:45 +0300
commitabef6da56913f1c55528103e60a50451a39628b1 (patch)
treeb3c8092471ecbb73e568cd0d336efa0e7871ee8d /src/Menus.c
initial commit
Diffstat (limited to 'src/Menus.c')
-rw-r--r--src/Menus.c4042
1 files changed, 4042 insertions, 0 deletions
diff --git a/src/Menus.c b/src/Menus.c
new file mode 100644
index 0000000..7e18b78
--- /dev/null
+++ b/src/Menus.c
@@ -0,0 +1,4042 @@
+#include "Menus.h"
+#include "Widgets.h"
+#include "Game.h"
+#include "Event.h"
+#include "Platform.h"
+#include "Inventory.h"
+#include "Drawer2D.h"
+#include "Graphics.h"
+#include "Funcs.h"
+#include "Model.h"
+#include "Generator.h"
+#include "Server.h"
+#include "Chat.h"
+#include "ExtMath.h"
+#include "Window.h"
+#include "Camera.h"
+#include "Http.h"
+#include "Block.h"
+#include "World.h"
+#include "Formats.h"
+#include "BlockPhysics.h"
+#include "MapRenderer.h"
+#include "TexturePack.h"
+#include "Audio.h"
+#include "Screens.h"
+#include "Gui.h"
+#include "Deflate.h"
+#include "Stream.h"
+#include "Builder.h"
+#include "Lighting.h"
+#include "Logger.h"
+#include "Options.h"
+#include "Input.h"
+#include "Utils.h"
+#include "Errors.h"
+#include "SystemFonts.h"
+#include "Lighting.h"
+
+/* Describes a menu option button */
+struct MenuOptionDesc {
+	short dir, y;
+	const char* name;
+	Widget_LeftClick OnClick;
+	Button_Get GetValue; Button_Set SetValue;
+};
+
+
+/*########################################################################################################################*
+*--------------------------------------------------------Menu base--------------------------------------------------------*
+*#########################################################################################################################*/
+void Menu_AddButtons(void* screen, struct ButtonWidget* btns, int width, const struct SimpleButtonDesc* descs, int count) {
+	int i;
+	for (i = 0; i < count; i++) {
+		ButtonWidget_Add(screen, &btns[i], width, descs[i].onClick);
+	}
+}
+
+void Menu_LayoutButtons(struct ButtonWidget* btns, const struct SimpleButtonDesc* descs, int count) {
+	int i;
+	for (i = 0; i < count; i++) {
+		Widget_SetLocation(&btns[i], ANCHOR_CENTRE, ANCHOR_CENTRE,  descs[i].x, descs[i].y);
+	}
+}
+
+void Menu_SetButtons(struct ButtonWidget* btns, struct FontDesc* font, const struct SimpleButtonDesc* descs, int count) {
+	int i;
+	for (i = 0; i < count; i++) {
+		ButtonWidget_SetConst(&btns[i], descs[i].title, font);
+	}
+}
+
+void Menu_LayoutBack(struct ButtonWidget* btn) {
+	Widget_SetLocation(btn, ANCHOR_CENTRE, ANCHOR_MAX, 0, 25);
+}
+static void Menu_CloseKeyboard(void* s) { OnscreenKeyboard_Close(); }
+
+static void Menu_RenderBounds(void) {
+	/* These were sourced by taking a screenshot of vanilla
+	Then using paint to extract the color components
+	Then using wolfram alpha to solve the glblendfunc equation */
+	PackedCol topCol    = PackedCol_Make(24, 24, 24, 105);
+	PackedCol bottomCol = PackedCol_Make(51, 51, 98, 162);
+	Gfx_Draw2DGradient(0, 0, Window_UI.Width, Window_UI.Height, topCol, bottomCol);
+}
+
+
+static void Menu_UnselectAll(struct Screen* s) {
+	int i;
+	/* TODO: pointer ID mask */
+
+	for (i = 0; i < s->numWidgets; i++) 
+	{
+		struct Widget* w = s->widgets[i];
+		if (w) w->active = false;
+	}
+}
+
+int Menu_PointerDown(void* screen, int id, int x, int y) {
+	Screen_DoPointerDown(screen, id, x, y); 
+	return TOUCH_TYPE_GUI;
+}
+
+static int Menu_DoPointerMove(void* screen, int id, int x, int y) {
+	struct Screen* s = (struct Screen*)screen;
+	struct Widget** widgets;
+	int i, count;
+
+	Menu_UnselectAll(s);
+	widgets = s->widgets;
+	count   = s->numWidgets;
+
+	for (i = count - 1; i >= 0; i--) 
+	{
+		struct Widget* w = widgets[i];
+		if (!w || !Widget_Contains(w, x, y)) continue;
+
+		w->active = true;
+		return i;
+	}
+	return -1;
+}
+
+int Menu_PointerMove(void* screen, int id, int x, int y) {
+	Menu_DoPointerMove(screen, id, x, y); return true;
+}
+
+static cc_bool Menu_IsSelectable(struct Widget* w) {
+	if (!w || (w->flags & WIDGET_FLAG_DISABLED)) return false;
+	if (!(w->flags & WIDGET_FLAG_SELECTABLE))    return false;
+
+	return true;
+}
+
+static void Menu_CycleSelected(struct Screen* s, int dir) {
+	struct Widget* w;
+	int index = s->selectedI + dir;
+	int i, j;
+
+	for (j = 0; j < s->numWidgets; j++) 
+	{
+		i = (index + j * dir) % s->numWidgets;
+		if (i < 0) i += s->numWidgets;
+
+		w = s->widgets[i];
+		if (!Menu_IsSelectable(w)) continue;
+
+		Menu_UnselectAll(s);
+		s->selectedI = i;
+		w->active    = true;
+		return;
+	}
+}
+
+static void Menu_ClickSelected(struct Screen* s) {
+	struct Widget* w;
+	if (s->selectedI < 0) return;
+	w = s->widgets[s->selectedI];
+
+	if (!Menu_IsSelectable(w)) return;
+	if (w->MenuClick) w->MenuClick(s, w);
+}
+
+int Menu_InputDown(void* screen, int key) {
+	struct Screen* s = (struct Screen*)screen;
+	
+	if (Input_IsUpButton(key)) {
+		Menu_CycleSelected(s, -1);
+	} else if (Input_IsDownButton(key)) {
+		Menu_CycleSelected(s, +1);
+	} else if (Input_IsEnterButton(key)) {
+		Menu_ClickSelected(s);
+	}
+	return Screen_InputDown(screen, key);
+}
+
+
+/*########################################################################################################################*
+*------------------------------------------------------Menu utilities-----------------------------------------------------*
+*#########################################################################################################################*/
+static void Menu_Remove(void* screen, int i) {
+	struct Screen* s = (struct Screen*)screen;
+	struct Widget** widgets = s->widgets;
+
+	if (widgets[i]) { Elem_Free(widgets[i]); }
+	widgets[i] = NULL;
+}
+
+static void Menu_BeginGen(int width, int height, int length) {
+	World_NewMap();
+	World_SetDimensions(width, height, length);
+
+	Gen_Start();
+	GeneratingScreen_Show();
+}
+
+static int Menu_Int(const cc_string* str)     { int v;   Convert_ParseInt(str, &v);   return v; }
+static float Menu_Float(const cc_string* str) { float v; Convert_ParseFloat(str, &v); return v; }
+static PackedCol Menu_HexCol(const cc_string* str) { 
+	cc_uint8 rgb[3]; 
+	PackedCol_TryParseHex(str, rgb); 
+	return PackedCol_Make(rgb[0], rgb[1], rgb[2], 255);
+}
+
+static void Menu_SwitchOptions(void* a, void* b)        { OptionsGroupScreen_Show(); }
+static void Menu_SwitchPause(void* a, void* b)          { Gui_ShowPauseMenu(); }
+static void Menu_SwitchClassicOptions(void* a, void* b) { ClassicOptionsScreen_Show(); }
+
+static void Menu_SwitchBindsClassic(void* a, void* b)      { ClassicBindingsScreen_Show(); }
+static void Menu_SwitchBindsClassicHacks(void* a, void* b) { ClassicHacksBindingsScreen_Show(); }
+static void Menu_SwitchBindsNormal(void* a, void* b)       { NormalBindingsScreen_Show(); }
+static void Menu_SwitchBindsHacks(void* a, void* b)        { HacksBindingsScreen_Show(); }
+static void Menu_SwitchBindsOther(void* a, void* b)        { OtherBindingsScreen_Show(); }
+static void Menu_SwitchBindsMouse(void* a, void* b)        { MouseBindingsScreen_Show(); }
+static void Menu_SwitchBindsHotbar(void* a, void* b)       { HotbarBindingsScreen_Show(); }
+static void SwitchBindsMain(void* s, void* w);
+
+static void Menu_SwitchMisc(void* a, void* b)      { MiscOptionsScreen_Show(); }
+static void Menu_SwitchChat(void* a, void* b)      { ChatOptionsScreen_Show(); }
+static void Menu_SwitchGui(void* a, void* b)       { GuiOptionsScreen_Show(); }
+static void Menu_SwitchGfx(void* a, void* b)       { GraphicsOptionsScreen_Show(); }
+static void Menu_SwitchHacks(void* a, void* b)     { HacksSettingsScreen_Show(); }
+static void Menu_SwitchEnv(void* a, void* b)       { EnvSettingsScreen_Show(); }
+static void Menu_SwitchNostalgia(void* a, void* b) { NostalgiaMenuScreen_Show(); }
+
+static void Menu_SwitchGenLevel(void* a, void* b)        { GenLevelScreen_Show(); }
+static void Menu_SwitchClassicGenLevel(void* a, void* b) { ClassicGenScreen_Show(); }
+static void Menu_SwitchLoadLevel(void* a, void* b)       { LoadLevelScreen_Show(); }
+static void Menu_SwitchSaveLevel(void* a, void* b)       { SaveLevelScreen_Show(); }
+static void Menu_SwitchTexPacks(void* a, void* b)        { TexturePackScreen_Show(); }
+static void Menu_SwitchHotkeys(void* a, void* b)         { HotkeyListScreen_Show(); }
+static void Menu_SwitchFont(void* a, void* b)            { FontListScreen_Show(); }
+
+
+/*########################################################################################################################*
+*--------------------------------------------------------ListScreen-------------------------------------------------------*
+*#########################################################################################################################*/
+struct ListScreen;
+#define LIST_SCREEN_ITEMS 5
+
+static struct ListScreen {
+	Screen_Body
+	struct ButtonWidget btns[LIST_SCREEN_ITEMS];
+	struct ButtonWidget left, right, done, action;
+	struct FontDesc font;
+	int currentIndex;
+	Widget_LeftClick EntryClick, DoneClick, ActionClick;
+	const char* actionText;
+	void (*LoadEntries)(struct ListScreen* s);
+	void (*UpdateEntry)(struct ListScreen* s, struct ButtonWidget* btn, const cc_string* text);
+	const char* titleText;
+	struct TextWidget title;
+	struct StringsBuffer entries;
+} ListScreen;
+
+static struct Widget* list_widgets[LIST_SCREEN_ITEMS + 4 + 1];
+#define LISTSCREEN_EMPTY "-"
+
+static void ListScreen_Layout(void* screen) {
+	struct ListScreen* s = (struct ListScreen*)screen;
+	int i;
+
+	for (i = 0; i < LIST_SCREEN_ITEMS; i++) { 
+		Widget_SetLocation(&s->btns[i],
+			ANCHOR_CENTRE, ANCHOR_CENTRE, 0, (i - 2) * 50);
+	}
+
+	if (Input_TouchMode) {
+		Widget_SetLocation(&s->done,   ANCHOR_CENTRE_MIN, ANCHOR_MAX, -150, 25);
+		Widget_SetLocation(&s->action, ANCHOR_CENTRE_MAX, ANCHOR_MAX, -150, 25);
+	} else {
+		Widget_SetLocation(&s->done,   ANCHOR_CENTRE, ANCHOR_MAX, 0, 25);
+		Widget_SetLocation(&s->action, ANCHOR_CENTRE, ANCHOR_MAX, 0, 70);
+	}
+
+	Widget_SetLocation(&s->left,  ANCHOR_CENTRE, ANCHOR_CENTRE, -220,    0);
+	Widget_SetLocation(&s->right, ANCHOR_CENTRE, ANCHOR_CENTRE,  220,    0);
+	Widget_SetLocation(&s->title, ANCHOR_CENTRE, ANCHOR_CENTRE,    0, -155);
+}
+
+static STRING_REF cc_string ListScreen_UNSAFE_Get(struct ListScreen* s, int index) {
+	static const cc_string str = String_FromConst(LISTSCREEN_EMPTY);
+
+	if (index >= 0 && index < s->entries.count) {
+		return StringsBuffer_UNSAFE_Get(&s->entries, index);
+	}
+	return str;
+}
+
+static void ListScreen_UpdateTitle(struct ListScreen* s) {
+	cc_string str; char strBuffer[STRING_SIZE];
+	int num, pages;
+	String_InitArray(str, strBuffer);
+	String_AppendConst(&str, s->titleText);
+
+	if (!Game_ClassicMode) {
+		num   = (s->currentIndex / LIST_SCREEN_ITEMS) + 1;
+		pages = Math_CeilDiv(s->entries.count, LIST_SCREEN_ITEMS);
+
+		if (pages == 0) pages = 1;
+		String_Format2(&str, " &7(page %i/%i)", &num, &pages);
+	}
+	TextWidget_Set(&s->title, &str, &s->font);
+}
+
+static void ListScreen_UpdatePage(struct ListScreen* s) {
+	int end = s->entries.count - LIST_SCREEN_ITEMS;
+	Widget_SetDisabled(&s->left,  s->currentIndex <= 0);
+	Widget_SetDisabled(&s->right, s->currentIndex >= end);
+	ListScreen_UpdateTitle(s);
+}
+
+static void ListScreen_UpdateEntry(struct ListScreen* s, struct ButtonWidget* button, const cc_string* text) {
+	ButtonWidget_Set(button, text, &s->font);
+}
+
+static void ListScreen_RedrawEntries(struct ListScreen* s) {
+	cc_string str;
+	int i;
+	for (i = 0; i < LIST_SCREEN_ITEMS; i++) {
+		str = ListScreen_UNSAFE_Get(s, s->currentIndex + i);
+		
+		Widget_SetDisabled(&s->btns[i], String_CaselessEqualsConst(&str, LISTSCREEN_EMPTY));
+		s->UpdateEntry(s, &s->btns[i], &str);
+	}
+}
+
+static void ListScreen_SetCurrentIndex(struct ListScreen* s, int index) {
+	if (index >= s->entries.count) { index = s->entries.count - 1; }
+	if (index < 0) index = 0;
+
+	s->currentIndex = index;
+	ListScreen_RedrawEntries(s);
+	ListScreen_UpdatePage(s);
+}
+
+static void ListScreen_PageClick(struct ListScreen* s, cc_bool forward) {
+	int delta = forward ? LIST_SCREEN_ITEMS : -LIST_SCREEN_ITEMS;
+	ListScreen_SetCurrentIndex(s, s->currentIndex + delta);
+}
+
+static void ListScreen_MoveBackwards(void* screen, void* b) {
+	struct ListScreen* s = (struct ListScreen*)screen;
+	ListScreen_PageClick(s, false);
+}
+
+static void ListScreen_MoveForwards(void* screen, void* b) {
+	struct ListScreen* s = (struct ListScreen*)screen;
+	ListScreen_PageClick(s, true);
+}
+
+static cc_string ListScreen_UNSAFE_GetCur(struct ListScreen* s, void* widget) {
+	struct ButtonWidget* btn = (struct ButtonWidget*)widget;
+	int i = btn->meta.val;
+	return ListScreen_UNSAFE_Get(s, s->currentIndex + i);
+}
+
+static void ListScreen_Select(struct ListScreen* s, const cc_string* str) {
+	cc_string entry;
+	int i;
+
+	for (i = 0; i < s->entries.count; i++) {
+		entry = StringsBuffer_UNSAFE_Get(&s->entries, i);
+		if (!String_CaselessEquals(&entry, str)) continue;
+
+		s->currentIndex = i;
+		return;
+	}
+}
+
+static int ListScreen_KeyDown(void* screen, int key) {
+	struct ListScreen* s = (struct ListScreen*)screen;
+
+	if (Input_IsLeftButton(key)         || key == CCKEY_PAGEUP) {
+		ListScreen_PageClick(s, false);
+	} else if (Input_IsRightButton(key) || key == CCKEY_PAGEDOWN) {
+		ListScreen_PageClick(s, true);
+	} else if (key == CCWHEEL_UP) {
+		ListScreen_SetCurrentIndex(s, s->currentIndex - 1);
+	} else if (key == CCWHEEL_DOWN) {
+		ListScreen_SetCurrentIndex(s, s->currentIndex + 1);
+	} else {
+		Menu_InputDown(screen, key);
+	}
+	return true;
+}
+
+static void ListScreen_Init(void* screen) {
+	struct ListScreen* s = (struct ListScreen*)screen;
+	int i, width;
+	s->widgets    = list_widgets;
+	s->numWidgets = 0;
+	s->maxWidgets = Array_Elems(list_widgets);
+	s->currentIndex = 0;
+
+	for (i = 0; i < LIST_SCREEN_ITEMS; i++) 
+	{
+		ButtonWidget_Add(s, &s->btns[i], 300, s->EntryClick);
+		s->btns[i].meta.val = i;
+	}
+	width = Input_TouchMode ? 140 : 400;
+	ButtonWidget_Add(s, &s->action, width, s->ActionClick);
+
+	ButtonWidget_Add(s, &s->left,  40, ListScreen_MoveBackwards);
+	ButtonWidget_Add(s, &s->right, 40, ListScreen_MoveForwards);
+	TextWidget_Add(s,   &s->title);
+	ButtonWidget_Add(s, &s->done,  width, s->DoneClick);
+
+	s->maxVertices = Screen_CalcDefaultMaxVertices(screen);
+	s->LoadEntries(s);
+}
+
+static void ListScreen_Render(void* screen, float delta) {
+	Menu_RenderBounds();
+	Screen_Render2Widgets(screen, delta);
+}
+
+static void ListScreen_Free(void* screen) {
+	struct ListScreen* s = (struct ListScreen*)screen;
+	StringsBuffer_Clear(&s->entries);
+}
+
+static void ListScreen_ContextLost(void* screen) {
+	struct ListScreen* s = (struct ListScreen*)screen;
+	Screen_ContextLost(screen);
+	Font_Free(&s->font);
+}
+
+static void ListScreen_ContextRecreated(void* screen) {
+	struct ListScreen* s = (struct ListScreen*)screen;
+	Screen_UpdateVb(screen);
+	Gui_MakeTitleFont(&s->font);
+	ListScreen_RedrawEntries(s);
+
+	ButtonWidget_SetConst(&s->left,  "<",   &s->font);
+	ButtonWidget_SetConst(&s->right, ">",   &s->font);
+	ButtonWidget_SetConst(&s->done, "Done", &s->font);
+	ListScreen_UpdatePage(s);
+
+	ButtonWidget_SetConst(&s->action, s->actionText, &s->font);
+}
+
+static void ListScreen_Reload(struct ListScreen* s) {
+	ListScreen_Free(s);
+	s->LoadEntries(s);
+	ListScreen_SetCurrentIndex(s, s->currentIndex);
+}
+
+static const struct ScreenVTABLE ListScreen_VTABLE = {
+	ListScreen_Init,    Screen_NullUpdate, ListScreen_Free,  
+	ListScreen_Render,  Screen_BuildMesh,
+	ListScreen_KeyDown, Screen_InputUp,    Screen_TKeyPress, Screen_TText,
+	Menu_PointerDown,   Screen_PointerUp,  Menu_PointerMove, Screen_TMouseScroll,
+	ListScreen_Layout,  ListScreen_ContextLost,  ListScreen_ContextRecreated
+};
+void ListScreen_Show(void) {
+	struct ListScreen* s = &ListScreen;
+	s->grabsInput = true;
+	s->closable   = true;
+	s->VTABLE     = &ListScreen_VTABLE;
+	Gui_Add((struct Screen*)s, GUI_PRIORITY_MENU);
+}
+
+
+/*########################################################################################################################*
+*--------------------------------------------------------MenuScreen-------------------------------------------------------*
+*#########################################################################################################################*/
+void MenuScreen_Render2(void* screen, float delta) {
+	Menu_RenderBounds();
+	Screen_Render2Widgets(screen, delta);
+}
+
+
+/*########################################################################################################################*
+*-----------------------------------------------------PauseScreenBase-----------------------------------------------------*
+*#########################################################################################################################*/
+#define PAUSE_MAX_BTNS 6
+static struct PauseScreen {
+	Screen_Body
+	int descsCount;
+	const struct SimpleButtonDesc* descs;
+	struct ButtonWidget btns[PAUSE_MAX_BTNS], quit, back;
+	struct TextWidget title, title2;
+} PauseScreen;
+
+static void PauseScreenBase_Quit(void* a, void* b) { Window_RequestClose(); }
+static void PauseScreenBase_Game(void* a, void* b) { Gui_Remove((struct Screen*)&PauseScreen); }
+
+static void PauseScreenBase_ContextRecreated(struct PauseScreen* s, struct FontDesc* titleFont) {
+	Screen_UpdateVb(s);
+	Gui_MakeTitleFont(titleFont);
+	//TODO - Make hack settings menu
+	Menu_SetButtons(s->btns, titleFont, s->descs, s->descsCount);
+	ButtonWidget_SetConst(&s->back, "Back to game", titleFont);
+	//TODO - Make credits be in bottom left
+	TextWidget_SetConst(&s->title,  "&eClassihax by &dWlodekM", titleFont);
+	// TextWidget_SetConst(&s->title2,  "&fGame menu", titleFont);
+}
+
+static void PauseScreenBase_AddWidgets(struct PauseScreen* s, int width) {
+	TextWidget_Add(s,   &s->title);
+	Menu_AddButtons(s,  s->btns, width, s->descs, s->descsCount);
+	ButtonWidget_Add(s, &s->back, 400, PauseScreenBase_Game);
+}
+
+
+/*########################################################################################################################*
+*-------------------------------------------------------PauseScreen-------------------------------------------------------*
+*#########################################################################################################################*/
+static struct Widget* pause_widgets[1 + 6 + 2];
+
+static void PauseScreen_CheckHacksAllowed(void* screen) {
+	struct PauseScreen* s = (struct PauseScreen*)screen;
+	if (Gui.ClassicMenu) return;
+
+	Widget_SetDisabled(&s->btns[1],
+			!Entities.CurPlayer->Hacks.CanAnyHacks); /* select texture pack */
+	s->dirty = true;
+}
+
+static void PauseScreen_ContextRecreated(void* screen) {
+	struct PauseScreen* s = (struct PauseScreen*)screen;
+	struct FontDesc titleFont;
+	PauseScreenBase_ContextRecreated(s, &titleFont);
+
+	ButtonWidget_SetConst(&s->quit, "Quit game", &titleFont);
+	PauseScreen_CheckHacksAllowed(s);
+	Font_Free(&titleFont);
+}
+
+static void PauseScreen_Layout(void* screen) {
+	struct PauseScreen* s = (struct PauseScreen*)screen;
+	Menu_LayoutButtons(s->btns, s->descs, s->descsCount);
+	Menu_LayoutBack(&s->back);
+	Widget_SetLocation(&s->quit,  ANCHOR_MAX,    ANCHOR_MAX,    5,    5);
+	Widget_SetLocation(&s->title, ANCHOR_CENTRE, ANCHOR_CENTRE, 0, -100);
+}
+
+static void PauseScreen_Init(void* screen) {
+	struct PauseScreen* s = (struct PauseScreen*)screen;
+	static const struct SimpleButtonDesc descs[] = {
+		{ -160,  -50, "Options...",             Menu_SwitchOptions   },
+		{ -160,    0, "Change texture pack...", Menu_SwitchTexPacks  },
+		{ -160,   50, "Hotkeys...",             Menu_SwitchHotkeys   },
+		{  160,  -50, "Generate new level...",  Menu_SwitchGenLevel  },
+		{  160,    0, "Load level...",          Menu_SwitchLoadLevel },
+		{  160,   50, "Save level...",          Menu_SwitchSaveLevel }
+	};
+	s->widgets     = pause_widgets;
+	s->numWidgets  = 0;
+	s->maxWidgets  = Array_Elems(pause_widgets);
+	Event_Register_(&UserEvents.HackPermsChanged, s, PauseScreen_CheckHacksAllowed);
+
+	s->descs      = descs;
+	s->descsCount = Array_Elems(descs);
+	PauseScreenBase_AddWidgets(s, 300);
+	ButtonWidget_Add(s, &s->quit, 120, PauseScreenBase_Quit);
+	s->maxVertices = Screen_CalcDefaultMaxVertices(s);
+
+	if (Server.IsSinglePlayer) return;
+	s->btns[3].flags = WIDGET_FLAG_DISABLED;
+	s->btns[4].flags = WIDGET_FLAG_DISABLED;
+}
+
+static void PauseScreen_Free(void* screen) {
+	Event_Unregister_(&UserEvents.HackPermsChanged, screen, PauseScreen_CheckHacksAllowed);
+}
+
+static const struct ScreenVTABLE PauseScreen_VTABLE = {
+	PauseScreen_Init,   Screen_NullUpdate, PauseScreen_Free, 
+	MenuScreen_Render2, Screen_BuildMesh,
+	Menu_InputDown,     Screen_InputUp,    Screen_TKeyPress, Screen_TText,
+	Menu_PointerDown,   Screen_PointerUp,  Menu_PointerMove, Screen_TMouseScroll,
+	PauseScreen_Layout, Screen_ContextLost, PauseScreen_ContextRecreated
+};
+void PauseScreen_Show(void) {
+	struct PauseScreen* s = &PauseScreen;
+	s->grabsInput = true;
+	s->closable   = true;
+	s->VTABLE     = &PauseScreen_VTABLE;
+	Gui_Add((struct Screen*)s, GUI_PRIORITY_MENU);
+}
+
+
+/*########################################################################################################################*
+*----------------------------------------------------ClassicPauseScreen---------------------------------------------------*
+*#########################################################################################################################*/
+static struct Widget* classicPause_widgets[1 + 5 + 1];
+
+static void ClassicPauseScreen_ContextRecreated(void* screen) {
+	struct PauseScreen* s = (struct PauseScreen*)screen;
+	struct FontDesc titleFont;
+	PauseScreenBase_ContextRecreated(s, &titleFont);
+	Font_Free(&titleFont);
+}
+
+static void ClassicPauseScreen_Layout(void* screen) {
+	struct PauseScreen* s = (struct PauseScreen*)screen;
+	Menu_LayoutButtons(s->btns, s->descs, s->descsCount);
+	Widget_SetLocation(&s->back,  ANCHOR_CENTRE, ANCHOR_MAX, 0, Game_ClassicMode ? 80 : 25);
+	Widget_SetLocation(&s->title, ANCHOR_CENTRE, ANCHOR_CENTRE, 0, -150);
+}
+
+static void ClassicPauseScreen_Init(void* screen) {
+	struct PauseScreen* s = (struct PauseScreen*)screen;
+	static const struct SimpleButtonDesc descs[] = {
+		{    0, -100, "Options...",             Menu_SwitchClassicOptions },
+		{    0,  -50, "Generate new level...",  Menu_SwitchClassicGenLevel },
+		{    0,    0, "Save level..",           Menu_SwitchSaveLevel },
+		{    0,   50, "Load level..",           Menu_SwitchLoadLevel },
+		{    0,  100, "Nostalgia options...",   Menu_SwitchNostalgia }
+	};
+	s->widgets    = classicPause_widgets;
+	s->numWidgets = 0;
+	s->maxWidgets = Array_Elems(classicPause_widgets);
+	s->descs      = descs;
+
+	/* Don't show nostalgia options in classic mode */
+	s->descsCount = Game_ClassicMode ? 4 : 5;
+	PauseScreenBase_AddWidgets(s, 400);
+	s->maxVertices = Screen_CalcDefaultMaxVertices(s);
+
+	if (Server.IsSinglePlayer) return;
+	s->btns[1].flags = WIDGET_FLAG_DISABLED;
+	s->btns[3].flags = WIDGET_FLAG_DISABLED;
+}
+
+static const struct ScreenVTABLE ClassicPauseScreen_VTABLE = {
+	ClassicPauseScreen_Init,   Screen_NullUpdate, Screen_NullFunc, 
+	MenuScreen_Render2,        Screen_BuildMesh,
+	Menu_InputDown,            Screen_InputUp,    Screen_TKeyPress, Screen_TText,
+	Menu_PointerDown,          Screen_PointerUp,  Menu_PointerMove, Screen_TMouseScroll,
+	ClassicPauseScreen_Layout, Screen_ContextLost, ClassicPauseScreen_ContextRecreated
+};
+void ClassicPauseScreen_Show(void) {
+	struct PauseScreen* s = &PauseScreen;
+	s->grabsInput = true;
+	s->closable   = true;
+	s->VTABLE     = &ClassicPauseScreen_VTABLE;
+	Gui_Add((struct Screen*)s, GUI_PRIORITY_MENU);
+}
+
+
+/*########################################################################################################################*
+*--------------------------------------------------OptionsGroupScreen-----------------------------------------------------*
+*#########################################################################################################################*/
+static struct OptionsGroupScreen {
+	Screen_Body
+	struct FontDesc textFont;
+	struct ButtonWidget btns[8];
+	struct TextWidget desc;
+	struct ButtonWidget done;
+} OptionsGroupScreen;
+
+static struct Widget* optGroups_widgets[8 + 2];
+
+static const char* const optsGroup_descs[8] = {
+	"&eMusic/Sound, view bobbing, and more",
+	"&eGui scale, font settings, and more",
+	"&eFPS limit, view distance, entity names/shadows",
+	"&eSet key and mouse bindings",
+	"&eChat options",
+	"&eHacks allowed, jump settings, and more",
+	"&eEnv colours, water level, weather, and more",
+	"&eSettings for resembling the original classic",
+};
+static const struct SimpleButtonDesc optsGroup_btns[8] = {
+	{ -160, -100, "Misc options...",      Menu_SwitchMisc        },
+	{ -160,  -50, "Gui options...",       Menu_SwitchGui         },
+	{ -160,    0, "Graphics options...",  Menu_SwitchGfx         },
+	{ -160,   50, "Controls...",          SwitchBindsMain        },
+	{  160, -100, "Chat options...",      Menu_SwitchChat        },
+	{  160,  -50, "Hacks settings...",    Menu_SwitchHacks       },
+	{  160,    0, "Env settings...",      Menu_SwitchEnv         },
+	{  160,   50, "Nostalgia options...", Menu_SwitchNostalgia   }
+};
+
+static void OptionsGroupScreen_CheckHacksAllowed(void* screen) {
+	struct OptionsGroupScreen* s = (struct OptionsGroupScreen*)screen;
+	Widget_SetDisabled(&s->btns[6],
+			!Entities.CurPlayer->Hacks.CanAnyHacks); /* env settings */
+	s->dirty = true;
+}
+
+CC_NOINLINE static void OptionsGroupScreen_UpdateDesc(struct OptionsGroupScreen* s) {
+	TextWidget_SetConst(&s->desc, optsGroup_descs[s->selectedI], &s->textFont);
+}
+
+static void OptionsGroupScreen_ContextLost(void* screen) {
+	struct OptionsGroupScreen* s = (struct OptionsGroupScreen*)screen;
+	Font_Free(&s->textFont);
+	Screen_ContextLost(screen);
+}
+
+static void OptionsGroupScreen_ContextRecreated(void* screen) {
+	struct OptionsGroupScreen* s = (struct OptionsGroupScreen*)screen;
+	struct FontDesc titleFont;
+	Screen_UpdateVb(screen);
+
+	Gui_MakeTitleFont(&titleFont);
+	Gui_MakeBodyFont(&s->textFont);
+
+	Menu_SetButtons(s->btns, &titleFont, optsGroup_btns, 8);
+	ButtonWidget_SetConst(&s->done, "Done", &titleFont);
+
+	if (s->selectedI >= 0) OptionsGroupScreen_UpdateDesc(s);
+	OptionsGroupScreen_CheckHacksAllowed(s);
+	Font_Free(&titleFont);
+}
+
+static void OptionsGroupScreen_Layout(void* screen) {
+	struct OptionsGroupScreen* s = (struct OptionsGroupScreen*)screen;
+	Menu_LayoutButtons(s->btns, optsGroup_btns, 8);
+	Widget_SetLocation(&s->desc, ANCHOR_CENTRE, ANCHOR_CENTRE, 0, 100);
+	Menu_LayoutBack(&s->done);
+}
+
+static void OptionsGroupScreen_Init(void* screen) {
+	struct OptionsGroupScreen* s = (struct OptionsGroupScreen*)screen;
+
+	Event_Register_(&UserEvents.HackPermsChanged, s, OptionsGroupScreen_CheckHacksAllowed);
+	s->widgets     = optGroups_widgets;
+	s->numWidgets  = 0;
+	s->maxWidgets  = Array_Elems(optGroups_widgets);
+	s->selectedI   = -1;
+
+	Menu_AddButtons(s,  s->btns, 300, optsGroup_btns, 8);
+	TextWidget_Add(s,   &s->desc);
+	ButtonWidget_Add(s, &s->done, 400, Menu_SwitchPause);
+
+	s->maxVertices = Screen_CalcDefaultMaxVertices(s);
+}
+
+static void OptionsGroupScreen_Free(void* screen) {
+	struct OptionsGroupScreen* s = (struct OptionsGroupScreen*)screen;
+	Event_Unregister_(&UserEvents.HackPermsChanged, s, OptionsGroupScreen_CheckHacksAllowed);
+}
+
+static int OptionsGroupScreen_PointerMove(void* screen, int id, int x, int y) {
+	struct OptionsGroupScreen* s = (struct OptionsGroupScreen*)screen;
+	int i = Menu_DoPointerMove(s, id, x, y);
+	if (i == -1 || i == s->selectedI) return true;
+	if (i >= Array_Elems(optsGroup_descs)) return true;
+
+	s->selectedI = i;
+	OptionsGroupScreen_UpdateDesc(s);
+	return true;
+}
+
+static const struct ScreenVTABLE OptionsGroupScreen_VTABLE = {
+	OptionsGroupScreen_Init,   Screen_NullUpdate, OptionsGroupScreen_Free,
+	MenuScreen_Render2,        Screen_BuildMesh,
+	Menu_InputDown,            Screen_InputUp,    Screen_TKeyPress,               Screen_TText,
+	Menu_PointerDown,          Screen_PointerUp,  OptionsGroupScreen_PointerMove, Screen_TMouseScroll,
+	OptionsGroupScreen_Layout, OptionsGroupScreen_ContextLost, OptionsGroupScreen_ContextRecreated
+};
+void OptionsGroupScreen_Show(void) {
+	struct OptionsGroupScreen* s = &OptionsGroupScreen;
+	s->grabsInput = true;
+	s->closable   = true;
+	s->VTABLE     = &OptionsGroupScreen_VTABLE;
+	Gui_Add((struct Screen*)s, GUI_PRIORITY_MENU);
+}
+
+
+/*########################################################################################################################*
+*----------------------------------------------------EditHotkeyScreen-----------------------------------------------------*
+*#########################################################################################################################*/
+static struct EditHotkeyScreen {
+	Screen_Body
+	struct HotkeyData curHotkey, origHotkey;
+	cc_bool supressNextPress;
+	int barX, barY[2], barWidth, barHeight;
+	struct FontDesc titleFont, textFont;
+	struct TextInputWidget input;
+	struct ButtonWidget btns[5], cancel;
+} EditHotkeyScreen;
+
+static struct Widget* edithotkey_widgets[1 + 5 + 1];
+
+static void HotkeyListScreen_MakeFlags(int flags, cc_string* str);
+static void EditHotkeyScreen_MakeFlags(int flags, cc_string* str) {
+	if (flags == 0) String_AppendConst(str, " None");
+	HotkeyListScreen_MakeFlags(flags, str);
+}
+
+static void EditHotkeyScreen_UpdateBaseKey(struct EditHotkeyScreen* s) {
+	cc_string text; char textBuffer[STRING_SIZE];
+	String_InitArray(text, textBuffer);
+
+	if (s->selectedI == 0) {
+		String_AppendConst(&text, "Key: press a key..");
+	} else {
+		String_AppendConst(&text, "Key: ");
+		String_AppendConst(&text, Input_DisplayNames[s->curHotkey.trigger]);
+	}
+	ButtonWidget_Set(&s->btns[0], &text, &s->titleFont);
+}
+
+static void EditHotkeyScreen_UpdateModifiers(struct EditHotkeyScreen* s) {
+	cc_string text; char textBuffer[STRING_SIZE];
+	String_InitArray(text, textBuffer);
+
+	if (s->selectedI == 1) {
+		String_AppendConst(&text, "Modifiers: press a key..");
+	} else {
+		String_AppendConst(&text, "Modifiers:");
+		EditHotkeyScreen_MakeFlags(s->curHotkey.mods, &text);
+	}
+	ButtonWidget_Set(&s->btns[1], &text, &s->titleFont);
+}
+
+static void EditHotkeyScreen_UpdateLeaveOpen(struct EditHotkeyScreen* s) {
+	cc_string text; char textBuffer[STRING_SIZE];
+	String_InitArray(text, textBuffer);
+
+	String_AppendConst(&text, "Input stays open: ");
+	String_AppendConst(&text, 
+		(s->curHotkey.flags & HOTKEY_FLAG_STAYS_OPEN) ? "ON" : "OFF");
+	ButtonWidget_Set(&s->btns[2], &text, &s->titleFont);
+}
+
+static void EditHotkeyScreen_BaseKey(void* screen, void* b) {
+	struct EditHotkeyScreen* s = (struct EditHotkeyScreen*)screen;
+	s->selectedI        = 0;
+	s->supressNextPress = true;
+	EditHotkeyScreen_UpdateBaseKey(s);
+}
+
+static void EditHotkeyScreen_Modifiers(void* screen, void* b) {
+	struct EditHotkeyScreen* s = (struct EditHotkeyScreen*)screen;
+	s->selectedI        = 1;
+	s->supressNextPress = true;
+	EditHotkeyScreen_UpdateModifiers(s);
+}
+
+static void EditHotkeyScreen_LeaveOpen(void* screen, void* b) {
+	struct EditHotkeyScreen* s = (struct EditHotkeyScreen*)screen;
+	/* Reset 'waiting for key..' state of two other buttons */
+	if (s->selectedI >= 0) {
+		s->selectedI        = -1;
+		s->supressNextPress = false;
+		EditHotkeyScreen_UpdateBaseKey(s);
+		EditHotkeyScreen_UpdateModifiers(s);
+	}
+	
+	/* Toggle Input Stays Open flag */
+	s->curHotkey.flags ^= HOTKEY_FLAG_STAYS_OPEN;
+	EditHotkeyScreen_UpdateLeaveOpen(s);
+}
+
+static void EditHotkeyScreen_SaveChanges(void* screen, void* b) {
+	struct EditHotkeyScreen* s = (struct EditHotkeyScreen*)screen;
+	struct HotkeyData hk = s->origHotkey;
+
+	if (hk.trigger) {
+		Hotkeys_Remove(hk.trigger, hk.mods);
+		StoredHotkeys_Remove(hk.trigger, hk.mods);
+	}
+	hk = s->curHotkey;
+
+	if (hk.trigger) {
+		cc_string text    = s->input.base.text;
+		cc_bool staysOpen = hk.flags & HOTKEY_FLAG_STAYS_OPEN;
+
+		Hotkeys_Add(hk.trigger, hk.mods, &text, hk.flags);
+		StoredHotkeys_Add(hk.trigger, hk.mods, staysOpen, &text);
+	}
+	HotkeyListScreen_Show();
+}
+
+static void EditHotkeyScreen_RemoveHotkey(void* screen, void* b) {
+	struct EditHotkeyScreen* s = (struct EditHotkeyScreen*)screen;
+	struct HotkeyData hk = s->origHotkey;
+
+	if (hk.trigger) {
+		Hotkeys_Remove(hk.trigger, hk.mods);
+		StoredHotkeys_Remove(hk.trigger, hk.mods);
+	}
+	HotkeyListScreen_Show();
+}
+
+static void EditHotkeyScreen_Render(void* screen, float delta) {
+	struct EditHotkeyScreen* s = (struct EditHotkeyScreen*)screen;
+	PackedCol grey = PackedCol_Make(150, 150, 150, 255);
+
+	MenuScreen_Render2(screen, delta);
+	Gfx_Draw2DFlat(s->barX, s->barY[0], s->barWidth, s->barHeight, grey);
+	Gfx_Draw2DFlat(s->barX, s->barY[1], s->barWidth, s->barHeight, grey);
+}
+
+static int EditHotkeyScreen_KeyPress(void* screen, char keyChar) {
+	struct EditHotkeyScreen* s = (struct EditHotkeyScreen*)screen;
+	if (s->supressNextPress) {
+		s->supressNextPress = false;
+	} else {
+		InputWidget_Append(&s->input.base, keyChar);
+	}
+	return true;
+}
+
+static int EditHotkeyScreen_TextChanged(void* screen, const cc_string* str) {
+	struct EditHotkeyScreen* s = (struct EditHotkeyScreen*)screen;
+	InputWidget_SetText(&s->input.base, str);
+	return true;
+}
+
+static int EditHotkeyScreen_KeyDown(void* screen, int key) {
+	struct EditHotkeyScreen* s = (struct EditHotkeyScreen*)screen;
+	if (s->selectedI >= 0) {
+		if (s->selectedI == 0) {
+			s->curHotkey.trigger = key;
+		} else if (s->selectedI == 1) {
+			if      (key == CCKEY_LCTRL  || key == CCKEY_RCTRL)  s->curHotkey.mods |= HOTKEY_MOD_CTRL;
+			else if (key == CCKEY_LSHIFT || key == CCKEY_RSHIFT) s->curHotkey.mods |= HOTKEY_MOD_SHIFT;
+			else if (key == CCKEY_LALT   || key == CCKEY_RALT)   s->curHotkey.mods |= HOTKEY_MOD_ALT;
+			else s->curHotkey.mods = 0;
+		}
+
+		s->supressNextPress = true;
+		s->selectedI        = -1;
+
+		EditHotkeyScreen_UpdateBaseKey(s);
+		EditHotkeyScreen_UpdateModifiers(s);
+		return true;
+	}
+	return Elem_HandlesKeyDown(&s->input.base, key) || Screen_InputDown(s, key);
+}
+
+static void EditHotkeyScreen_ContextLost(void* screen) {
+	struct EditHotkeyScreen* s = (struct EditHotkeyScreen*)screen;
+	Font_Free(&s->titleFont);
+	Font_Free(&s->textFont);
+	Screen_ContextLost(screen);
+}
+
+static void EditHotkeyScreen_ContextRecreated(void* screen) {
+	struct EditHotkeyScreen* s = (struct EditHotkeyScreen*)screen;
+	cc_bool existed = s->origHotkey.trigger != INPUT_NONE;
+
+	Gui_MakeTitleFont(&s->titleFont);
+	Gui_MakeBodyFont(&s->textFont);
+	Screen_UpdateVb(screen);
+
+	EditHotkeyScreen_UpdateBaseKey(s);
+	EditHotkeyScreen_UpdateModifiers(s);
+	EditHotkeyScreen_UpdateLeaveOpen(s);
+
+	ButtonWidget_SetConst(&s->btns[3], existed ? "Save changes" : "Add hotkey", &s->titleFont);
+	ButtonWidget_SetConst(&s->btns[4], existed ? "Remove hotkey" : "Cancel",    &s->titleFont);
+	TextInputWidget_SetFont(&s->input, &s->textFont);
+	ButtonWidget_SetConst(&s->cancel, "Cancel", &s->titleFont);
+}
+
+static void EditHotkeyScreen_Update(void* screen, float delta) {
+	struct EditHotkeyScreen* s = (struct EditHotkeyScreen*)screen;
+	s->input.base.caretAccumulator += delta;
+}
+
+static void EditHotkeyScreen_Layout(void* screen) {
+	struct EditHotkeyScreen* s = (struct EditHotkeyScreen*)screen;
+	s->barWidth  = Display_ScaleX(500);
+	s->barX      = Gui_CalcPos(ANCHOR_CENTRE, 0, s->barWidth, Window_UI.Width);
+	s->barHeight = Display_ScaleY(2);
+
+	s->barY[0] = Gui_CalcPos(ANCHOR_CENTRE, Display_ScaleY(-65), 
+					s->barHeight, Window_UI.Height);
+	s->barY[1] = Gui_CalcPos(ANCHOR_CENTRE, Display_ScaleY( 45), 
+					s->barHeight, Window_UI.Height);
+
+	Widget_SetLocation(&s->btns[0], ANCHOR_CENTRE, ANCHOR_CENTRE,    0, -150);
+	Widget_SetLocation(&s->btns[1], ANCHOR_CENTRE, ANCHOR_CENTRE,    0, -100);
+	Widget_SetLocation(&s->btns[2], ANCHOR_CENTRE, ANCHOR_CENTRE, -100,   10);
+	Widget_SetLocation(&s->btns[3], ANCHOR_CENTRE, ANCHOR_CENTRE,    0,   80);
+	Widget_SetLocation(&s->btns[4], ANCHOR_CENTRE, ANCHOR_CENTRE,    0,  130);
+	Widget_SetLocation(&s->input,   ANCHOR_CENTRE, ANCHOR_CENTRE,    0,  -35);
+	Menu_LayoutBack(&s->cancel);
+}
+
+static void EditHotkeyScreen_Init(void* screen) {
+	struct EditHotkeyScreen* s = (struct EditHotkeyScreen*)screen;
+	struct MenuInputDesc desc;
+	cc_string text;
+
+	s->widgets     = edithotkey_widgets;
+	s->numWidgets  = 0;
+	s->maxWidgets  = Array_Elems(edithotkey_widgets);
+	
+	s->selectedI   = -1;
+	MenuInput_String(desc);
+
+	ButtonWidget_Add(s, &s->btns[0], 300, EditHotkeyScreen_BaseKey);
+	ButtonWidget_Add(s, &s->btns[1], 300, EditHotkeyScreen_Modifiers);
+	ButtonWidget_Add(s, &s->btns[2], 300, EditHotkeyScreen_LeaveOpen);
+	ButtonWidget_Add(s, &s->btns[3], 300, EditHotkeyScreen_SaveChanges);
+	ButtonWidget_Add(s, &s->btns[4], 300, EditHotkeyScreen_RemoveHotkey);
+
+	if (s->origHotkey.trigger) {
+		text = StringsBuffer_UNSAFE_Get(&HotkeysText, s->origHotkey.textIndex);
+	} else { text = String_Empty; }
+
+	TextInputWidget_Add(s, &s->input, 500, &text, &desc);
+	ButtonWidget_Add(s,    &s->cancel, 400, Menu_SwitchHotkeys);
+	s->input.onscreenPlaceholder = "Hotkey text";
+
+	s->maxVertices = Screen_CalcDefaultMaxVertices(s);
+}
+
+static const struct ScreenVTABLE EditHotkeyScreen_VTABLE = {
+	EditHotkeyScreen_Init,    EditHotkeyScreen_Update, Menu_CloseKeyboard,
+	EditHotkeyScreen_Render,  Screen_BuildMesh,
+	EditHotkeyScreen_KeyDown, Screen_InputUp,    EditHotkeyScreen_KeyPress, EditHotkeyScreen_TextChanged,
+	Menu_PointerDown,         Screen_PointerUp,  Menu_PointerMove,          Screen_TMouseScroll,
+	EditHotkeyScreen_Layout,  EditHotkeyScreen_ContextLost, EditHotkeyScreen_ContextRecreated
+};
+void EditHotkeyScreen_Show(struct HotkeyData original) {
+	struct EditHotkeyScreen* s = &EditHotkeyScreen;
+	s->grabsInput = true;
+	s->closable   = true;
+	s->VTABLE     = &EditHotkeyScreen_VTABLE;
+	s->origHotkey = original;
+	s->curHotkey  = original;
+	Gui_Add((struct Screen*)s, GUI_PRIORITY_MENU);
+}
+
+
+/*########################################################################################################################*
+*-----------------------------------------------------GenLevelScreen------------------------------------------------------*
+*#########################################################################################################################*/
+static struct GenLevelScreen {
+	Screen_Body
+	struct FontDesc textFont;
+	struct ButtonWidget flatgrass, vanilla, cancel;
+	struct TextInputWidget inputs[4];
+	struct TextWidget labels[4], title;
+} GenLevelScreen;
+#define GENLEVEL_NUM_INPUTS 4
+
+static struct Widget* gen_widgets[2 * GENLEVEL_NUM_INPUTS + 4];
+
+CC_NOINLINE static int GenLevelScreen_GetInt(struct GenLevelScreen* s, int index) {
+	struct TextInputWidget* input = &s->inputs[index];
+	struct MenuInputDesc* desc;
+	cc_string text = input->base.text;
+	int value;
+
+	desc = &input->desc;
+	if (!desc->VTABLE->IsValidValue(desc, &text)) return 0;
+	Convert_ParseInt(&text, &value); return value;
+}
+
+CC_NOINLINE static int GenLevelScreen_GetSeedInt(struct GenLevelScreen* s, int index) {
+	struct TextInputWidget* input = &s->inputs[index];
+	RNGState rnd;
+
+	if (!input->base.text.length) {
+		Random_SeedFromCurrentTime(&rnd);
+		return Random_Next(&rnd, Int32_MaxValue);
+	}
+	return GenLevelScreen_GetInt(s, index);
+}
+
+static void GenLevelScreen_Gen(void* screen, const struct MapGenerator* gen) {
+	struct GenLevelScreen* s = (struct GenLevelScreen*)screen;
+	int width  = GenLevelScreen_GetInt(s, 0);
+	int height = GenLevelScreen_GetInt(s, 1);
+	int length = GenLevelScreen_GetInt(s, 2);
+	int seed   = GenLevelScreen_GetSeedInt(s, 3);
+
+	cc_uint64 volume = (cc_uint64)width * height * length;
+	if (volume > Int32_MaxValue) {
+		Chat_AddRaw("&cThe generated map's volume is too big.");
+	} else if (!width || !height || !length) {
+		Chat_AddRaw("&cOne of the map dimensions is invalid.");
+	} else {
+		Gen_Active  = gen;
+		Gen_Seed    = seed;
+		Gui_Remove((struct Screen*)s);
+		Menu_BeginGen(width, height, length);
+	}
+}
+
+static void GenLevelScreen_Flatgrass(void* a, void* b) { GenLevelScreen_Gen(a, &FlatgrassGen); }
+static void GenLevelScreen_Notchy(void* a, void* b)    { GenLevelScreen_Gen(a, &NotchyGen);    }
+
+static void GenLevelScreen_Make(struct GenLevelScreen* s, int i, int def) {
+	cc_string tmp; char tmpBuffer[STRING_SIZE];
+	struct MenuInputDesc desc;
+
+	if (i == 3) {
+		MenuInput_Seed(desc);
+	} else {
+		MenuInput_Int(desc, 1, 8192, def);
+	}
+
+	String_InitArray(tmp, tmpBuffer);
+	desc.VTABLE->GetDefault(&desc, &tmp);
+
+	TextWidget_Add(s, &s->labels[i]);
+	s->labels[i].color = PackedCol_Make(224, 224, 224, 255);
+	
+	/* TODO placeholder */
+	TextInputWidget_Add(s, &s->inputs[i], 200, &tmp, &desc);
+	s->inputs[i].base.showCaret = false;
+	s->inputs[i].onscreenType = KEYBOARD_TYPE_INTEGER;
+	s->inputs[i].base.meta.val = 10000;
+}
+#define GenLevelScreen_IsInput(w) (w)->meta.val == 10000
+
+static struct TextInputWidget* GenLevelScreen_SelectedInput(struct GenLevelScreen* s) {
+	if (s->selectedI >= 0 && GenLevelScreen_IsInput(s->widgets[s->selectedI])) {
+		return (struct TextInputWidget*)s->widgets[s->selectedI];
+	}
+	return NULL;
+}
+
+static int GenLevelScreen_KeyDown(void* screen, int key) {
+	struct GenLevelScreen* s = (struct GenLevelScreen*)screen;
+	struct TextInputWidget* selected = GenLevelScreen_SelectedInput(s);
+	struct MenuInputDesc* desc;
+
+	if (selected) {
+		if (Elem_HandlesKeyDown(&selected->base, key)) return true;
+
+		desc = &selected->desc;
+		if (desc->VTABLE->ProcessInput(desc, &selected->base.text, key)) return true;
+	}
+	return Menu_InputDown(s, key);
+}
+
+static int GenLevelScreen_KeyPress(void* screen, char keyChar) {
+	struct GenLevelScreen* s = (struct GenLevelScreen*)screen;
+	struct TextInputWidget* selected = GenLevelScreen_SelectedInput(s);
+
+	if (selected) InputWidget_Append(&selected->base, keyChar);
+	return true;
+}
+
+static int GenLevelScreen_TextChanged(void* screen, const cc_string* str) {
+	struct GenLevelScreen* s = (struct GenLevelScreen*)screen;
+	struct TextInputWidget* selected = GenLevelScreen_SelectedInput(s);
+
+	if (selected) InputWidget_SetText(&selected->base, str);
+	return true;
+}
+
+static int GenLevelScreen_PointerDown(void* screen, int id, int x, int y) {
+	struct GenLevelScreen* s = (struct GenLevelScreen*)screen;
+	struct TextInputWidget* selected;
+	s->selectedI = Screen_DoPointerDown(screen, id, x, y);
+
+	selected = GenLevelScreen_SelectedInput(s);
+	if (selected) OnscreenKeyboard_SetText(&selected->base.text);
+	return TOUCH_TYPE_GUI;
+}
+
+static void GenLevelScreen_ContextLost(void* screen) {
+	struct GenLevelScreen* s = (struct GenLevelScreen*)screen;
+	Font_Free(&s->textFont);
+	Screen_ContextLost(screen);
+}
+
+static void GenLevelScreen_ContextRecreated(void* screen) {
+	struct GenLevelScreen* s  = (struct GenLevelScreen*)screen;
+	struct FontDesc titleFont;
+	Gui_MakeTitleFont(&titleFont);
+	Gui_MakeBodyFont(&s->textFont);
+	Screen_UpdateVb(screen);
+
+	TextInputWidget_SetFont(&s->inputs[0], &s->textFont);
+	TextInputWidget_SetFont(&s->inputs[1], &s->textFont);
+	TextInputWidget_SetFont(&s->inputs[2], &s->textFont);
+	TextInputWidget_SetFont(&s->inputs[3], &s->textFont);
+
+	TextWidget_SetConst(&s->labels[0], "Width:",  &s->textFont);
+	TextWidget_SetConst(&s->labels[1], "Height:", &s->textFont);
+	TextWidget_SetConst(&s->labels[2], "Length:", &s->textFont);
+	TextWidget_SetConst(&s->labels[3], "Seed:",   &s->textFont);
+	
+	TextWidget_SetConst(&s->title,       "Generate new level", &s->textFont);
+	ButtonWidget_SetConst(&s->flatgrass, "Flatgrass",          &titleFont);
+	ButtonWidget_SetConst(&s->vanilla,   "Vanilla",            &titleFont);
+	ButtonWidget_SetConst(&s->cancel,    "Cancel",             &titleFont);
+	Font_Free(&titleFont);
+}
+
+static void GenLevelScreen_Update(void* screen, float delta) {
+	struct GenLevelScreen* s = (struct GenLevelScreen*)screen;
+	struct TextInputWidget* selected = GenLevelScreen_SelectedInput(s);
+	int i;
+	
+	for (i = 0; i < GENLEVEL_NUM_INPUTS; i++)
+	{
+		s->inputs[i].base.showCaret = &s->inputs[i] == selected;
+	}
+	if (selected) selected->base.caretAccumulator += delta;
+}
+
+static void GenLevelScreen_Layout(void* screen) {
+	struct GenLevelScreen* s = (struct GenLevelScreen*)screen;
+	int i, y;
+	for (i = 0; i < 4; i++) {
+		y = (i - 2) * 40;
+		Widget_SetLocation(&s->inputs[i], ANCHOR_CENTRE,     ANCHOR_CENTRE,   0, y);
+		Widget_SetLocation(&s->labels[i], ANCHOR_CENTRE_MAX, ANCHOR_CENTRE, 110, y);
+	}
+
+	Widget_SetLocation(&s->title,     ANCHOR_CENTRE, ANCHOR_CENTRE,    0, -130);
+	Widget_SetLocation(&s->flatgrass, ANCHOR_CENTRE, ANCHOR_CENTRE, -120,  100);
+	Widget_SetLocation(&s->vanilla,   ANCHOR_CENTRE, ANCHOR_CENTRE,  120,  100);
+	Menu_LayoutBack(&s->cancel);
+}
+
+static void GenLevelScreen_Init(void* screen) {
+	struct GenLevelScreen* s = (struct GenLevelScreen*)screen;
+	s->widgets    = gen_widgets;
+	s->numWidgets = 0;
+	s->maxWidgets = Array_Elems(gen_widgets);
+	s->selectedI  = -1;
+
+	GenLevelScreen_Make(s, 0, World.Width);
+	GenLevelScreen_Make(s, 1, World.Height);
+	GenLevelScreen_Make(s, 2, World.Length);
+	GenLevelScreen_Make(s, 3, 0);
+
+	TextWidget_Add(s,   &s->title);
+	ButtonWidget_Add(s, &s->flatgrass, 200, GenLevelScreen_Flatgrass);
+	ButtonWidget_Add(s, &s->vanilla,   200, GenLevelScreen_Notchy);
+	ButtonWidget_Add(s, &s->cancel,    400, Menu_SwitchPause);
+
+	s->maxVertices = Screen_CalcDefaultMaxVertices(s);
+}
+
+static const struct ScreenVTABLE GenLevelScreen_VTABLE = {
+	GenLevelScreen_Init,        GenLevelScreen_Update, Menu_CloseKeyboard,
+	MenuScreen_Render2,         Screen_BuildMesh,
+	GenLevelScreen_KeyDown,     Screen_InputUp,    GenLevelScreen_KeyPress, GenLevelScreen_TextChanged,
+	GenLevelScreen_PointerDown, Screen_PointerUp,  Menu_PointerMove,        Screen_TMouseScroll,
+	GenLevelScreen_Layout,      GenLevelScreen_ContextLost, GenLevelScreen_ContextRecreated
+};
+void GenLevelScreen_Show(void) {	
+	struct GenLevelScreen* s = &GenLevelScreen;
+	s->grabsInput = true;
+	s->closable   = true;
+	s->VTABLE     = &GenLevelScreen_VTABLE;
+	Gui_Add((struct Screen*)s, GUI_PRIORITY_MENU);
+}
+
+
+/*########################################################################################################################*
+*----------------------------------------------------ClassicGenScreen-----------------------------------------------------*
+*#########################################################################################################################*/
+static struct ClassicGenScreen {
+	Screen_Body
+	struct ButtonWidget btns[3], cancel;
+	struct TextWidget title;
+} ClassicGenScreen;
+
+static struct Widget* classicgen_widgets[1 + 3 + 1];
+
+static void ClassicGenScreen_Gen(int size) {
+	RNGState rnd; Random_SeedFromCurrentTime(&rnd);
+	Gen_Active = &NotchyGen;
+	Gen_Seed   = Random_Next(&rnd, Int32_MaxValue);
+
+	Gui_Remove((struct Screen*)&ClassicGenScreen);
+	Menu_BeginGen(size, 64, size);
+}
+
+static void ClassicGenScreen_Small(void* a, void* b)  { ClassicGenScreen_Gen(128); }
+static void ClassicGenScreen_Medium(void* a, void* b) { ClassicGenScreen_Gen(256); }
+static void ClassicGenScreen_Huge(void* a, void* b)   { ClassicGenScreen_Gen(512); }
+
+static void ClassicGenScreen_ContextRecreated(void* screen) {
+	struct ClassicGenScreen* s = (struct ClassicGenScreen*)screen;
+	struct FontDesc titleFont;
+
+	Screen_UpdateVb(screen);
+	Gui_MakeTitleFont(&titleFont);
+	TextWidget_SetConst(&s->title, "Generate new level", &titleFont);
+
+	ButtonWidget_SetConst(&s->btns[0], "Small",  &titleFont);
+	ButtonWidget_SetConst(&s->btns[1], "Normal", &titleFont);
+	ButtonWidget_SetConst(&s->btns[2], "Huge",   &titleFont);
+	ButtonWidget_SetConst(&s->cancel,  "Cancel", &titleFont);
+	Font_Free(&titleFont);
+}
+
+static void ClassicGenScreen_Layout(void* screen) {
+	struct ClassicGenScreen* s = (struct ClassicGenScreen*)screen;
+	Widget_SetLocation(&s->title,   ANCHOR_CENTRE, ANCHOR_CENTRE, 0, -150);
+	Widget_SetLocation(&s->btns[0], ANCHOR_CENTRE, ANCHOR_CENTRE, 0, -100);
+	Widget_SetLocation(&s->btns[1], ANCHOR_CENTRE, ANCHOR_CENTRE, 0,  -50);
+	Widget_SetLocation(&s->btns[2], ANCHOR_CENTRE, ANCHOR_CENTRE, 0,    0);
+	Widget_SetLocation(&s->cancel,  ANCHOR_CENTRE, ANCHOR_MAX,    0,   80);
+}
+
+static void ClassicGenScreen_Init(void* screen) {
+	struct ClassicGenScreen* s = (struct ClassicGenScreen*)screen;
+	s->widgets     = classicgen_widgets;
+	s->numWidgets  = 0;
+	s->maxWidgets  = Array_Elems(classicgen_widgets);
+
+	TextWidget_Add(s,   &s->title);
+	ButtonWidget_Add(s, &s->btns[0], 400, ClassicGenScreen_Small);
+	ButtonWidget_Add(s, &s->btns[1], 400, ClassicGenScreen_Medium);
+	ButtonWidget_Add(s, &s->btns[2], 400, ClassicGenScreen_Huge);
+	ButtonWidget_Add(s, &s->cancel,  400, Menu_SwitchPause);
+
+	s->maxVertices = Screen_CalcDefaultMaxVertices(s);
+}
+
+static const struct ScreenVTABLE ClassicGenScreen_VTABLE = {
+	ClassicGenScreen_Init,   Screen_NullUpdate,  Screen_NullFunc,
+	MenuScreen_Render2,      Screen_BuildMesh,
+	Menu_InputDown,          Screen_InputUp,     Screen_TKeyPress, Screen_TText,
+	Menu_PointerDown,        Screen_PointerUp,   Menu_PointerMove, Screen_TMouseScroll,
+	ClassicGenScreen_Layout, Screen_ContextLost, ClassicGenScreen_ContextRecreated
+};
+void ClassicGenScreen_Show(void) {
+	struct ClassicGenScreen* s = &ClassicGenScreen;
+	s->grabsInput = true;
+	s->closable   = true;
+	s->VTABLE     = &ClassicGenScreen_VTABLE;
+	Gui_Add((struct Screen*)s, GUI_PRIORITY_MENU);
+}
+
+
+/*########################################################################################################################*
+*----------------------------------------------------SaveLevelScreen------------------------------------------------------*
+*#########################################################################################################################*/
+static struct SaveLevelScreen {
+	Screen_Body
+	struct FontDesc titleFont, textFont;
+	struct ButtonWidget save, file, cancel;
+	struct TextInputWidget input;
+	struct TextWidget desc;
+} SaveLevelScreen;
+
+static struct Widget* save_widgets[3 + 1 + 1];
+
+static void SaveLevelScreen_UpdateSave(struct SaveLevelScreen* s) {
+	ButtonWidget_SetConst(&s->save, 
+		s->save.optName ? "&cOverwrite existing?" : "Save", &s->titleFont);
+}
+
+static void SaveLevelScreen_RemoveOverwrites(struct SaveLevelScreen* s) {
+	if (s->save.optName) {
+		s->save.optName = NULL;
+		SaveLevelScreen_UpdateSave(s);
+	}
+}
+
+static cc_result SaveLevelScreen_SaveMap(const cc_string* path) {
+	static const cc_string schematic = String_FromConst(".schematic");
+	static const cc_string mine = String_FromConst(".mine");
+	struct Stream stream, compStream;
+	struct GZipState state;
+	cc_result res;
+
+	res = Stream_CreateFile(&stream, path);
+	if (res) { Logger_SysWarn2(res, "creating", path); return res; }
+	GZip_MakeStream(&compStream, &state, &stream);
+
+	if (String_CaselessEnds(path, &schematic)) {
+		res = Schematic_Save(&compStream);
+	} else if (String_CaselessEnds(path, &mine)) {
+		res = Dat_Save(&compStream);
+	} else {
+		res = Cw_Save(&compStream);
+	}
+
+	if (res) {
+		stream.Close(&stream);
+		Logger_SysWarn2(res, "encoding", path); return res;
+	}
+
+	if ((res = compStream.Close(&compStream))) {
+		stream.Close(&stream);
+		Logger_SysWarn2(res, "closing", path); return res;
+	}
+
+	res = stream.Close(&stream);
+	if (res) { Logger_SysWarn2(res, "closing", path); return res; }
+
+	World.LastSave = Game.Time;
+	Gui_ShowPauseMenu();
+	return 0;
+}
+
+static void SaveLevelScreen_Save(void* screen, void* widget) { 
+	struct SaveLevelScreen* s = (struct SaveLevelScreen*)screen;
+	struct ButtonWidget* btn  = (struct ButtonWidget*)widget;
+	cc_string path; char pathBuffer[FILENAME_SIZE];
+	cc_string file = s->input.base.text;
+	cc_result res;
+
+	if (!file.length) {
+		TextWidget_SetConst(&s->desc, "&ePlease enter a filename", &s->textFont);
+		return;
+	}
+
+	String_InitArray(path, pathBuffer);
+	String_Format1(&path, "maps/%s.cw", &file);
+	String_Copy(&World.Name, &file);
+
+	if (File_Exists(&path) && !btn->optName) {
+		btn->optName = "";
+		SaveLevelScreen_UpdateSave(s);
+		return;
+	}
+		
+	SaveLevelScreen_RemoveOverwrites(s);
+	if ((res = SaveLevelScreen_SaveMap(&path))) return;
+	Chat_Add1("&eSaved map to: %s", &path);
+}
+
+static void SaveLevelScreen_UploadCallback(const cc_string* path) {
+	cc_result res = SaveLevelScreen_SaveMap(path);
+	if (!res) Chat_Add1("&eSaved map to: %s", path);
+}
+
+static void SaveLevelScreen_File(void* screen, void* b) {
+	static const char* const titles[] = {
+		"ClassiCube map", "Minecraft schematic", "Minecraft classic map", NULL
+	};
+	static const char* const filters[] = {
+		".cw", ".schematic", ".mine", NULL
+	};
+	struct SaveLevelScreen* s = (struct SaveLevelScreen*)screen;
+	struct SaveFileDialogArgs args;
+	cc_result res;
+
+	args.filters     = filters;
+	args.titles      = titles;
+	args.defaultName = s->input.base.text;
+	args.Callback    = SaveLevelScreen_UploadCallback;
+
+	res = Window_SaveFileDialog(&args);
+	if (res == SFD_ERR_NEED_DEFAULT_NAME) {
+		TextWidget_SetConst(&s->desc, "&ePlease enter a filename", &s->textFont);
+	} else if (res) {
+		Logger_SimpleWarn(res, "showing save file dialog");
+	}
+}
+
+static int SaveLevelScreen_KeyPress(void* screen, char keyChar) {
+	struct SaveLevelScreen* s = (struct SaveLevelScreen*)screen;
+	SaveLevelScreen_RemoveOverwrites(s);
+	InputWidget_Append(&s->input.base, keyChar);
+	return true;
+}
+
+static int SaveLevelScreen_TextChanged(void* screen, const cc_string* str) {
+	struct SaveLevelScreen* s = (struct SaveLevelScreen*)screen;
+	SaveLevelScreen_RemoveOverwrites(s);
+	InputWidget_SetText(&s->input.base, str);
+	return true;
+}
+
+static int SaveLevelScreen_KeyDown(void* screen, int key) {
+	struct SaveLevelScreen* s = (struct SaveLevelScreen*)screen;
+	if (Elem_HandlesKeyDown(&s->input.base, key)) {
+		SaveLevelScreen_RemoveOverwrites(s);
+		return true;
+	}
+	return Menu_InputDown(s, key);
+}
+
+static void SaveLevelScreen_ContextLost(void* screen) {
+	struct SaveLevelScreen* s = (struct SaveLevelScreen*)screen;
+	Font_Free(&s->titleFont);
+	Font_Free(&s->textFont);
+	Screen_ContextLost(screen);
+}
+
+static void SaveLevelScreen_ContextRecreated(void* screen) {
+	struct SaveLevelScreen* s = (struct SaveLevelScreen*)screen;
+	Gui_MakeTitleFont(&s->titleFont);
+	Gui_MakeBodyFont(&s->textFont);
+
+	Screen_UpdateVb(screen);
+	SaveLevelScreen_UpdateSave(s);
+
+	TextInputWidget_SetFont(&s->input,                &s->textFont);
+	ButtonWidget_SetConst(&s->cancel, "Cancel",       &s->titleFont);
+#ifdef CC_BUILD_WEB
+	ButtonWidget_SetConst(&s->file,   "Download",     &s->titleFont);
+#else
+	ButtonWidget_SetConst(&s->file,   "Save file...", &s->titleFont);
+#endif
+}
+
+static void SaveLevelScreen_Update(void* screen, float delta) {
+	struct SaveLevelScreen* s = (struct SaveLevelScreen*)screen;
+	s->input.base.caretAccumulator += delta;
+}
+
+static void SaveLevelScreen_Layout(void* screen) {
+	struct SaveLevelScreen* s = (struct SaveLevelScreen*)screen;
+	Widget_SetLocation(&s->input, ANCHOR_CENTRE, ANCHOR_CENTRE, 0, -30);
+	Widget_SetLocation(&s->save,  ANCHOR_CENTRE, ANCHOR_CENTRE, 0,  20);
+	Widget_SetLocation(&s->desc,  ANCHOR_CENTRE, ANCHOR_CENTRE, 0,  65);
+
+	Widget_SetLocation(&s->file, ANCHOR_CENTRE, ANCHOR_MAX, 0, 70);
+	Menu_LayoutBack(&s->cancel);
+}
+
+static void SaveLevelScreen_Init(void* screen) {
+	struct SaveLevelScreen* s = (struct SaveLevelScreen*)screen;
+	struct MenuInputDesc desc;
+	
+	s->widgets     = save_widgets;
+	s->numWidgets  = 0;
+	s->maxWidgets  = Array_Elems(save_widgets);
+	MenuInput_Path(desc);
+	
+	ButtonWidget_Add(s, &s->save, 400, SaveLevelScreen_Save);
+	ButtonWidget_Add(s, &s->file, 400, SaveLevelScreen_File);
+
+	ButtonWidget_Add(s, &s->cancel, 400, Menu_SwitchPause);
+	TextInputWidget_Add(s, &s->input, 400, &World.Name, &desc);
+	TextWidget_Add(s, &s->desc);
+	s->input.onscreenPlaceholder = "Map name";
+
+	s->maxVertices = Screen_CalcDefaultMaxVertices(s);
+}
+
+static const struct ScreenVTABLE SaveLevelScreen_VTABLE = {
+	SaveLevelScreen_Init,    SaveLevelScreen_Update, Menu_CloseKeyboard,
+	MenuScreen_Render2,      Screen_BuildMesh,
+	SaveLevelScreen_KeyDown, Screen_InputUp,   SaveLevelScreen_KeyPress, SaveLevelScreen_TextChanged,
+	Menu_PointerDown,        Screen_PointerUp, Menu_PointerMove,         Screen_TMouseScroll,
+	SaveLevelScreen_Layout,  SaveLevelScreen_ContextLost, SaveLevelScreen_ContextRecreated
+};
+void SaveLevelScreen_Show(void) {
+	struct SaveLevelScreen* s = &SaveLevelScreen;
+	s->grabsInput = true;
+	s->closable   = true;
+	s->VTABLE = &SaveLevelScreen_VTABLE;
+	Gui_Add((struct Screen*)s, GUI_PRIORITY_MENU);
+}
+
+
+/*########################################################################################################################*
+*---------------------------------------------------TexturePackScreen-----------------------------------------------------*
+*#########################################################################################################################*/
+static void TexturePackScreen_EntryClick(void* screen, void* widget) {
+	struct ListScreen* s = (struct ListScreen*)screen;
+	cc_string file = ListScreen_UNSAFE_GetCur(s, widget);
+	cc_result res;
+
+	TexturePack_SetDefault(&file);
+	TexturePack_Url.length = 0;
+	res = TexturePack_ExtractCurrent(true);
+
+	/* FileNotFound error may be because user deleted .zips from disc */
+	if (res != ReturnCode_FileNotFound) return;
+	Chat_AddRaw("&eReloading texture pack list as it may be out of date");
+	ListScreen_Reload(s);
+}
+
+static void TexturePackScreen_FilterFiles(const cc_string* path, void* obj) {
+	static const cc_string zip = String_FromConst(".zip");
+	cc_string relPath = *path;
+	if (!String_CaselessEnds(path, &zip)) return;
+
+	Utils_UNSAFE_TrimFirstDirectory(&relPath);
+	StringsBuffer_Add((struct StringsBuffer*)obj, &relPath);
+}
+
+static void TexturePackScreen_LoadEntries(struct ListScreen* s) {
+	static const cc_string path = String_FromConst("texpacks");
+	Directory_Enum(&path, &s->entries, TexturePackScreen_FilterFiles);
+	StringsBuffer_Sort(&s->entries);
+}
+
+static void TexturePackScreen_UploadCallback(const cc_string* path) {
+#ifdef CC_BUILD_WEB
+	cc_string relPath = *path;
+	Utils_UNSAFE_GetFilename(&relPath);
+
+	ListScreen_Reload(&ListScreen);
+	TexturePack_SetDefault(&relPath);
+#else
+	String_Copy(&TexturePack_Path, path);
+#endif
+	TexturePack_ExtractCurrent(true);
+}
+
+static void TexturePackScreen_ActionFunc(void* s, void* w) {
+	static const char* const filters[] = { 
+		".zip", NULL 
+	};
+	static struct OpenFileDialogArgs args = {
+		"Texture packs", filters,
+		TexturePackScreen_UploadCallback,
+		OFD_UPLOAD_PERSIST, "texpacks"
+	};
+
+	cc_result res = Window_OpenFileDialog(&args);
+	if (res) Logger_SimpleWarn(res, "showing open file dialog");
+}
+
+void TexturePackScreen_Show(void) {
+	struct ListScreen* s = &ListScreen;
+	s->titleText   = "Select a texture pack";
+#ifdef CC_BUILD_WEB
+	s->actionText = "Upload";
+#else
+	s->actionText = "Load file...";
+#endif
+
+	s->ActionClick = TexturePackScreen_ActionFunc;
+	s->LoadEntries = TexturePackScreen_LoadEntries;
+	s->EntryClick  = TexturePackScreen_EntryClick;
+	s->DoneClick   = Menu_SwitchPause;
+	s->UpdateEntry = ListScreen_UpdateEntry;
+	ListScreen_Show();
+}
+
+
+/*########################################################################################################################*
+*----------------------------------------------------FontListScreen-------------------------------------------------------*
+*#########################################################################################################################*/
+static void FontListScreen_EntryClick(void* screen, void* widget) {
+	struct ListScreen* s = (struct ListScreen*)screen;
+	cc_string fontName   = ListScreen_UNSAFE_GetCur(s, widget);
+
+	Options_Set(OPT_FONT_NAME, &fontName);
+	SysFont_SetDefault(&fontName);
+}
+
+static void FontListScreen_UpdateEntry(struct ListScreen* s, struct ButtonWidget* button, const cc_string* text) {
+	struct FontDesc font;
+	cc_result res;
+
+	if (String_CaselessEqualsConst(text, LISTSCREEN_EMPTY)) {
+		ButtonWidget_Set(button, text, &s->font); return;
+	}
+	res = SysFont_Make(&font, text, 16, FONT_FLAGS_NONE);
+
+	if (!res) {
+		ButtonWidget_Set(button, text, &font);
+	} else {
+		Logger_SimpleWarn2(res, "making font", text);
+		ButtonWidget_Set(button, text, &s->font);
+	}
+	Font_Free(&font);
+}
+
+static void FontListScreen_LoadEntries(struct ListScreen* s) {
+	SysFonts_GetNames(&s->entries);
+	ListScreen_Select(s, SysFonts_UNSAFE_GetDefault());
+}
+
+static void FontListScreen_RegisterCallback(const cc_string* path) {
+	Chat_Add1("Loaded font from %s", path);
+}
+
+static void FontListScreen_UploadCallback(const cc_string* path) { 
+	cc_result res = SysFonts_Register(path, FontListScreen_RegisterCallback);
+
+	if (res) {
+		Logger_SimpleWarn2(res, "loading font from", path);
+	} else {
+		SysFonts_SaveCache();
+	}
+}
+
+static void FontListScreen_ActionFunc(void* s, void* w) {
+	static const char* const filters[] = {
+		".ttf", ".otf", NULL
+	};
+	static struct OpenFileDialogArgs args = {
+		"Font files", filters,
+		FontListScreen_UploadCallback,
+		OFD_UPLOAD_DELETE, "tmp"
+	};
+
+	cc_result res = Window_OpenFileDialog(&args);
+	if (res) Logger_SimpleWarn(res, "showing open file dialog");
+}
+
+void FontListScreen_Show(void) {
+	struct ListScreen* s = &ListScreen;
+	s->titleText   = "Select a font";
+	s->actionText  = "Load font...";
+	s->ActionClick = FontListScreen_ActionFunc;
+
+	s->LoadEntries = FontListScreen_LoadEntries;
+	s->EntryClick  = FontListScreen_EntryClick;
+	s->DoneClick   = Menu_SwitchGui;
+	s->UpdateEntry = FontListScreen_UpdateEntry;
+	ListScreen_Show();
+}
+
+
+/*########################################################################################################################*
+*---------------------------------------------------HotkeyListScreen------------------------------------------------------*
+*#########################################################################################################################*/
+/* TODO: Hotkey added event for CPE */
+static void HotkeyListScreen_EntryClick(void* screen, void* widget) {
+	struct ListScreen* s = (struct ListScreen*)screen;
+	struct HotkeyData h, original = { 0 };
+	cc_string text, key, value;
+	int trigger;
+	int i, mods = 0;
+
+	text = ListScreen_UNSAFE_GetCur(s, widget);
+
+	String_UNSAFE_Separate(&text, '+', &key, &value);
+	if (String_ContainsConst(&value, "Ctrl"))  mods |= HOTKEY_MOD_CTRL;
+	if (String_ContainsConst(&value, "Shift")) mods |= HOTKEY_MOD_SHIFT;
+	if (String_ContainsConst(&value, "Alt"))   mods |= HOTKEY_MOD_ALT;
+
+	trigger = Utils_ParseEnum(&key, INPUT_NONE, Input_DisplayNames, INPUT_COUNT);
+	for (i = 0; i < HotkeysText.count; i++) {
+		h = HotkeysList[i];
+		if (h.trigger == trigger && h.mods == mods) { original = h; break; }
+	}
+
+	EditHotkeyScreen_Show(original);
+}
+
+static void HotkeyListScreen_MakeFlags(int flags, cc_string* str) {
+	if (flags & HOTKEY_MOD_CTRL)  String_AppendConst(str, " Ctrl");
+	if (flags & HOTKEY_MOD_SHIFT) String_AppendConst(str, " Shift");
+	if (flags & HOTKEY_MOD_ALT)   String_AppendConst(str, " Alt");
+}
+
+static void HotkeyListScreen_LoadEntries(struct ListScreen* s) {
+	cc_string text; char textBuffer[STRING_SIZE];
+	struct HotkeyData hKey;
+	int i;
+	String_InitArray(text, textBuffer);
+
+	for (i = 0; i < HotkeysText.count; i++) {
+		hKey = HotkeysList[i];
+		text.length = 0;
+		String_AppendConst(&text, Input_DisplayNames[hKey.trigger]);
+
+		if (hKey.mods) {
+			String_AppendConst(&text, " +");
+			HotkeyListScreen_MakeFlags(hKey.mods, &text);
+		}
+		StringsBuffer_Add(&s->entries, &text);
+	}
+	StringsBuffer_Sort(&s->entries);
+}
+
+static void HotkeyListScreen_UpdateEntry(struct ListScreen* s, struct ButtonWidget* button, const cc_string* text) {
+	if (text->length) ButtonWidget_Set(button, text, &s->font);
+}
+
+static void HotkeyListScreen_ActionFunc(void* s, void* w) {
+	struct HotkeyData original = { 0 };
+	EditHotkeyScreen_Show(original);
+}
+
+void HotkeyListScreen_Show(void) {
+	struct ListScreen* s = &ListScreen;
+	s->titleText   = "Modify hotkeys";
+	s->actionText  = "New hotkey...";
+	
+	s->ActionClick = HotkeyListScreen_ActionFunc;
+	s->LoadEntries = HotkeyListScreen_LoadEntries;
+	s->EntryClick  = HotkeyListScreen_EntryClick;
+	s->DoneClick   = Menu_SwitchPause;
+	s->UpdateEntry = HotkeyListScreen_UpdateEntry;
+	ListScreen_Show();
+}
+
+
+/*########################################################################################################################*
+*----------------------------------------------------LoadLevelScreen------------------------------------------------------*
+*#########################################################################################################################*/
+static void LoadLevelScreen_EntryClick(void* screen, void* widget) {
+	cc_string path; char pathBuffer[FILENAME_SIZE];
+	struct ListScreen* s = (struct ListScreen*)screen;
+	cc_result res;
+
+	cc_string relPath = ListScreen_UNSAFE_GetCur(s, widget);
+	String_InitArray(path, pathBuffer);
+	String_Format1(&path, "maps/%s", &relPath);
+	res = Map_LoadFrom(&path);
+
+	/* FileNotFound error may be because user deleted maps from disc */
+	if (res != ReturnCode_FileNotFound) return;
+	Chat_AddRaw("&eReloading level list as it may be out of date");
+	ListScreen_Reload(s);
+}
+
+static void LoadLevelScreen_FilterFiles(const cc_string* path, void* obj) {
+	struct MapImporter* imp = MapImporter_Find(path);
+	cc_string relPath = *path;
+	if (!imp) return;
+
+	Utils_UNSAFE_TrimFirstDirectory(&relPath);
+	StringsBuffer_Add((struct StringsBuffer*)obj, &relPath);
+}
+
+static void LoadLevelScreen_LoadEntries(struct ListScreen* s) {
+	static const cc_string path = String_FromConst("maps");
+	Directory_Enum(&path, &s->entries, LoadLevelScreen_FilterFiles);
+	StringsBuffer_Sort(&s->entries);
+}
+
+static void LoadLevelScreen_UploadCallback(const cc_string* path) { Map_LoadFrom(path); }
+static void LoadLevelScreen_ActionFunc(void* s, void* w) {
+	static const char* const filters[] = { 
+		".cw", ".dat", ".lvl", ".mine", ".fcm", ".mclevel", NULL 
+	}; /* TODO not hardcode list */
+	static struct OpenFileDialogArgs args = {
+		"Classic map files", filters,
+		LoadLevelScreen_UploadCallback,
+		OFD_UPLOAD_DELETE, "tmp"
+	};
+
+	cc_result res = Window_OpenFileDialog(&args);
+	if (res) Logger_SimpleWarn(res, "showing open file dialog");
+}
+
+void LoadLevelScreen_Show(void) {
+	struct ListScreen* s = &ListScreen;
+	s->titleText   = "Load level";
+#ifdef CC_BUILD_WEB
+	s->actionText = "Upload";
+#else
+	s->actionText = "Load file...";
+#endif
+	
+	s->ActionClick = LoadLevelScreen_ActionFunc;
+	s->LoadEntries = LoadLevelScreen_LoadEntries;
+	s->EntryClick  = LoadLevelScreen_EntryClick;
+	s->DoneClick   = Menu_SwitchPause;
+	s->UpdateEntry = ListScreen_UpdateEntry;
+	ListScreen_Show();
+}
+
+
+/*########################################################################################################################*
+*----------------------------------------------------BindSourcesScreen----------------------------------------------------*
+*#########################################################################################################################*/
+static struct BindsSourceScreen {
+	Screen_Body
+	struct ButtonWidget btns[2], cancel;
+} BindsSourceScreen;
+static int binds_gamepad; /* Default to Normal (Keyboard/Mouse) */
+
+static struct Widget* bindsSource_widgets[3];
+
+static void BindsSourceScreen_ModeNormal(void* screen, void* b) {
+	binds_gamepad = false;
+	NormalBindingsScreen_Show();
+}
+
+static void BindsSourceScreen_ModeGamepad(void* screen, void* b) {
+	binds_gamepad = true;
+	NormalBindingsScreen_Show();
+}
+
+static void BindsSourceScreen_ContextRecreated(void* screen) {
+	struct BindsSourceScreen* s = (struct BindsSourceScreen*)screen;
+	struct FontDesc font;
+	Gui_MakeTitleFont(&font);
+	Screen_UpdateVb(screen);
+
+	ButtonWidget_SetConst(&s->btns[0], "Keyboard/Mouse",     &font);
+	ButtonWidget_SetConst(&s->btns[1], "Gamepad/Controller", &font);
+	ButtonWidget_SetConst(&s->cancel,  "Cancel",             &font);
+	Font_Free(&font);
+}
+
+static void BindsSourceScreen_Layout(void* screen) {
+	struct BindsSourceScreen* s = (struct BindsSourceScreen*)screen;
+	Widget_SetLocation(&s->btns[0], ANCHOR_CENTRE, ANCHOR_CENTRE, 0, -25);
+	Widget_SetLocation(&s->btns[1], ANCHOR_CENTRE, ANCHOR_CENTRE, 0,  25);
+	Menu_LayoutBack(&s->cancel);
+}
+
+static void BindsSourceScreen_Init(void* screen) {
+	struct BindsSourceScreen* s = (struct BindsSourceScreen*)screen;
+
+	s->widgets     = bindsSource_widgets;
+	s->numWidgets  = 0;
+	s->maxWidgets  = Array_Elems(bindsSource_widgets);
+	s->selectedI   = -1;
+
+	ButtonWidget_Add(s, &s->btns[0], 300, BindsSourceScreen_ModeNormal);
+	ButtonWidget_Add(s, &s->btns[1], 300, BindsSourceScreen_ModeGamepad);
+	ButtonWidget_Add(s, &s->cancel,  400, Menu_SwitchPause);
+
+	s->maxVertices = Screen_CalcDefaultMaxVertices(s);
+}
+
+static const struct ScreenVTABLE BindsSourceScreen_VTABLE = {
+	BindsSourceScreen_Init,    Screen_NullUpdate, Screen_NullFunc,
+	MenuScreen_Render2,        Screen_BuildMesh,
+	Menu_InputDown,            Screen_InputUp,    Screen_TKeyPress, Screen_TText,
+	Menu_PointerDown,          Screen_PointerUp,  Menu_PointerMove, Screen_TMouseScroll,
+	BindsSourceScreen_Layout,  Screen_ContextLost, BindsSourceScreen_ContextRecreated
+};
+void BindsSourceScreen_Show(void) {
+	struct BindsSourceScreen* s = &BindsSourceScreen;
+	s->grabsInput = true;
+	s->closable   = true;
+	s->VTABLE     = &BindsSourceScreen_VTABLE;
+	Gui_Add((struct Screen*)s, GUI_PRIORITY_MENU);
+}
+
+static void SwitchBindsMain(void* s, void* w) {
+	if (Input.Sources == (INPUT_SOURCE_NORMAL | INPUT_SOURCE_GAMEPAD)) {
+		/* User needs to decide whether to configure mouse/keyboard or gamepad */
+		BindsSourceScreen_Show();
+	} else if (Input.Sources == INPUT_SOURCE_GAMEPAD) {
+		binds_gamepad = true;
+		NormalBindingsScreen_Show();
+	} else {
+		binds_gamepad = false;
+		NormalBindingsScreen_Show();
+	}
+}
+
+
+/*########################################################################################################################*
+*---------------------------------------------------KeyBindsScreen-----------------------------------------------------*
+*#########################################################################################################################*/
+struct KeyBindsScreen;
+typedef void (*InitKeyBindings)(struct KeyBindsScreen* s);
+#define KEYBINDS_MAX_BTNS 12
+
+static struct KeyBindsScreen {
+	Screen_Body	
+	int curI, bindsCount;
+	const char* const* descs;
+	const cc_uint8* binds;
+	Widget_LeftClick leftPage, rightPage;
+	int btnWidth, topY, arrowsY, leftLen;
+	const char* titleText;
+	const char* msgText;
+	struct FontDesc titleFont;
+	struct TextWidget title, msg;
+	struct ButtonWidget back, left, right;
+	struct ButtonWidget buttons[KEYBINDS_MAX_BTNS];
+} KeyBindsScreen;
+
+static struct Widget* key_widgets[KEYBINDS_MAX_BTNS + 5];
+
+static BindMapping KeyBindsScreen_GetBinding(struct KeyBindsScreen* s, int i) {
+	const BindMapping* curBinds;
+
+	curBinds = binds_gamepad ? PadBind_Mappings : KeyBind_Mappings;
+	return curBinds[s->binds[i]];
+}
+
+static void KeyBindsScreen_Update(struct KeyBindsScreen* s, int i) {
+	cc_string text; char textBuffer[STRING_SIZE];
+	BindMapping curBind; 
+
+	String_InitArray(text, textBuffer);
+	curBind = KeyBindsScreen_GetBinding(s, i);
+
+	String_Format4(&text, s->curI == i ? "> %c: %c%c%c <" : "%c: %c%c%c", 
+		s->descs[i],
+		Input_DisplayNames[curBind.button1],
+		curBind.button2 ? " + " : "",
+		curBind.button2 ? Input_DisplayNames[curBind.button2] : "");
+		
+	ButtonWidget_Set(&s->buttons[i], &text, &s->titleFont);
+	s->dirty = true;
+}
+
+static void KeyBindsScreen_OnBindingClick(void* screen, void* widget) {
+	struct KeyBindsScreen* s = (struct KeyBindsScreen*)screen;
+	struct ButtonWidget* btn = (struct ButtonWidget*)widget;
+	
+	int old     = s->curI;
+	s->curI     = btn->meta.val;
+	s->closable = false;
+
+	KeyBindsScreen_Update(s, s->curI);
+	/* previously selected a different button for binding */
+	if (old >= 0) KeyBindsScreen_Update(s, old);
+}
+
+static void KeyBindsScreen_ResetBinding(InputBind bind) {
+	if (binds_gamepad) {
+		PadBind_Reset(bind);
+	} else {
+		KeyBind_Reset(bind);
+	}
+}
+
+static void KeyBindsScreen_UpdateBinding(InputBind bind, int key) {
+	if (binds_gamepad) {
+		PadBind_Set(bind, key);
+	} else {
+		KeyBind_Set(bind, key);
+	}
+}
+
+static int KeyBindsScreen_KeyDown(void* screen, int key) {
+	struct KeyBindsScreen* s = (struct KeyBindsScreen*)screen;
+	InputBind bind;
+	int idx;
+
+	if (s->curI == -1) return Menu_InputDown(s, key);
+	bind = s->binds[s->curI];
+	
+	if (Input_IsEscapeButton(key)) {
+		KeyBindsScreen_ResetBinding(bind);
+	} else {
+		KeyBindsScreen_UpdateBinding(bind, key);
+	}
+
+	idx         = s->curI;
+	s->curI     = -1;
+	s->closable = true;
+	KeyBindsScreen_Update(s, idx);
+	return true;
+}
+
+static void KeyBindsScreen_ContextLost(void* screen) {
+	struct KeyBindsScreen* s = (struct KeyBindsScreen*)screen;
+	Font_Free(&s->titleFont);
+	Screen_ContextLost(screen);
+}
+
+static void KeyBindsScreen_ContextRecreated(void* screen) {
+	struct KeyBindsScreen* s = (struct KeyBindsScreen*)screen;
+	struct FontDesc textFont;
+	int i;
+
+	Screen_UpdateVb(screen);
+	Gui_MakeTitleFont(&s->titleFont);
+	Gui_MakeBodyFont(&textFont);
+	for (i = 0; i < s->bindsCount; i++) { KeyBindsScreen_Update(s, i); }
+
+	TextWidget_SetConst(&s->title, s->titleText, &s->titleFont);
+	TextWidget_SetConst(&s->msg,   s->msgText,   &textFont);
+	ButtonWidget_SetConst(&s->back, "Done",      &s->titleFont);
+
+	Font_Free(&textFont);
+	if (!s->leftPage && !s->rightPage) return;	
+	ButtonWidget_SetConst(&s->left,  "<", &s->titleFont);
+	ButtonWidget_SetConst(&s->right, ">", &s->titleFont);
+}
+
+static void KeyBindsScreen_Layout(void* screen) {
+	struct KeyBindsScreen* s = (struct KeyBindsScreen*)screen;
+	int i, x, y, xDir, leftLen;
+	x = s->btnWidth / 2 + 5;
+	y = s->topY;
+	
+	leftLen = s->leftLen;
+	for (i = 0; i < s->bindsCount; i++) {
+		if (i == leftLen) y = s->topY; /* reset y for next column */
+		xDir = leftLen == -1 ? 0 : (i < leftLen ? -1 : 1);
+
+		Widget_SetLocation(&s->buttons[i], ANCHOR_CENTRE, ANCHOR_CENTRE, x * xDir, y);
+		y += 50; /* distance between buttons */
+	}
+
+	Widget_SetLocation(&s->title, ANCHOR_CENTRE, ANCHOR_CENTRE, 0, -180);
+	Widget_SetLocation(&s->msg,   ANCHOR_CENTRE, ANCHOR_CENTRE, 0,  100);
+	Menu_LayoutBack(&s->back);
+	
+	Widget_SetLocation(&s->left,  ANCHOR_CENTRE, ANCHOR_CENTRE, -s->btnWidth - 35, s->arrowsY);
+	Widget_SetLocation(&s->right, ANCHOR_CENTRE, ANCHOR_CENTRE,  s->btnWidth + 35, s->arrowsY);
+}
+
+static void KeyBindsScreen_Init(void* screen) {
+	struct KeyBindsScreen* s = (struct KeyBindsScreen*)screen;
+	int i;
+	s->widgets     = key_widgets;
+	s->numWidgets  = 0;
+	s->maxWidgets  = Array_Elems(key_widgets);
+	s->curI        = -1;
+
+	for (i = 0; i < s->bindsCount; i++) 
+	{
+		ButtonWidget_Add(s, &s->buttons[i], s->btnWidth, KeyBindsScreen_OnBindingClick);
+		s->widgets[i] = (struct Widget*)&s->buttons[i];
+		s->buttons[i].meta.val = i;
+	}
+
+	TextWidget_Add(s,   &s->title);
+	TextWidget_Add(s,   &s->msg);
+	ButtonWidget_Add(s, &s->back, 400, Gui.ClassicMenu ? Menu_SwitchClassicOptions : Menu_SwitchOptions);
+
+	if (s->leftPage || s->rightPage) {
+		ButtonWidget_Add(s, &s->left,  40, s->leftPage);
+		ButtonWidget_Add(s, &s->right, 40, s->rightPage);
+		Widget_SetDisabled(&s->left,   !s->leftPage);
+		Widget_SetDisabled(&s->right,  !s->rightPage);
+	}
+
+	s->maxVertices = Screen_CalcDefaultMaxVertices(s);
+}
+
+static const struct ScreenVTABLE KeyBindsScreen_VTABLE = {
+	KeyBindsScreen_Init,    Screen_NullUpdate, Screen_NullFunc,  
+	MenuScreen_Render2,     Screen_BuildMesh,
+	KeyBindsScreen_KeyDown, Screen_InputUp,    Screen_TKeyPress, Screen_TText,
+	Menu_PointerDown,       Screen_PointerUp,  Menu_PointerMove, Screen_TMouseScroll,
+	KeyBindsScreen_Layout,  KeyBindsScreen_ContextLost, KeyBindsScreen_ContextRecreated
+};
+
+static void KeyBindsScreen_Reset(Widget_LeftClick left, Widget_LeftClick right, int btnWidth) {
+	struct KeyBindsScreen* s = &KeyBindsScreen;
+	s->leftPage  = left;
+	s->rightPage = right;
+	s->btnWidth  = btnWidth;
+	s->msgText   = "";
+}
+static void KeyBindsScreen_SetLayout(int topY, int arrowsY, int leftLen) {
+	struct KeyBindsScreen* s = &KeyBindsScreen;
+	s->topY    = topY;
+	s->arrowsY = arrowsY;
+	s->leftLen = leftLen;
+}
+static void KeyBindsScreen_Show(int bindsCount, const cc_uint8* binds, const char* const* descs, const char* title) {
+	struct KeyBindsScreen* s = &KeyBindsScreen;
+	s->grabsInput = true;
+	s->closable   = true;
+	s->VTABLE     = &KeyBindsScreen_VTABLE;
+
+	s->titleText  = title;
+	s->bindsCount = bindsCount;
+	s->binds      = binds;
+	s->descs      = descs;
+	Gui_Add((struct Screen*)s, GUI_PRIORITY_MENU);
+}
+
+
+/*########################################################################################################################*
+*------------------------------------------------ClassicBindingsScreen----------------------------------------------------*
+*#########################################################################################################################*/
+void ClassicBindingsScreen_Show(void) {
+	static const cc_uint8 binds[]    = { BIND_FORWARD, BIND_BACK, BIND_JUMP, BIND_CHAT, BIND_SET_SPAWN, BIND_LEFT, BIND_RIGHT, BIND_INVENTORY, BIND_FOG, BIND_RESPAWN };
+	static const char* const descs[] = { "Forward", "Back", "Jump", "Chat", "Save location", "Left", "Right", "Build", "Toggle fog", "Load location" };
+	binds_gamepad = false;
+
+	if (Game_ClassicHacks) {
+		KeyBindsScreen_Reset(NULL, Menu_SwitchBindsClassicHacks, 260);
+	} else {
+		KeyBindsScreen_Reset(NULL,                         NULL, 300);
+	}
+	KeyBindsScreen_SetLayout(-140, -40, 5);
+	KeyBindsScreen_Show(Array_Elems(binds), binds, descs, 
+						Game_ClassicHacks ? "Normal controls" : "Controls");
+}
+
+
+/*########################################################################################################################*
+*----------------------------------------------ClassicHacksBindingsScreen-------------------------------------------------*
+*#########################################################################################################################*/
+void ClassicHacksBindingsScreen_Show(void) {
+	static const cc_uint8 binds[6]    = { BIND_SPEED, BIND_NOCLIP, BIND_HALF_SPEED, BIND_FLY, BIND_FLY_UP, BIND_FLY_DOWN };
+	static const char* const descs[6] = { "Speed", "Noclip", "Half speed", "Fly", "Fly up", "Fly down" };
+	binds_gamepad = false;
+
+	KeyBindsScreen_Reset(Menu_SwitchBindsClassic, NULL, 260);
+	KeyBindsScreen_SetLayout(-90, -40, 3);
+	KeyBindsScreen_Show(Array_Elems(binds), binds, descs, "Hacks controls");
+}
+
+
+/*########################################################################################################################*
+*-------------------------------------------------NormalBindingsScreen----------------------------------------------------*
+*#########################################################################################################################*/
+void NormalBindingsScreen_Show(void) {
+	static const cc_uint8 binds[]    = { BIND_FORWARD, BIND_BACK, BIND_JUMP, BIND_CHAT, BIND_SET_SPAWN, BIND_TABLIST, BIND_LEFT, BIND_RIGHT, BIND_INVENTORY, BIND_FOG, BIND_RESPAWN, BIND_SEND_CHAT };
+	static const char* const descs[] = { "Forward", "Back", "Jump", "Chat", "Set spawn", "Player list", "Left", "Right", "Inventory", "Toggle fog", "Respawn", "Send chat" };
+	
+	KeyBindsScreen_Reset(NULL, Menu_SwitchBindsHacks, 250);
+	KeyBindsScreen_SetLayout(-140, 10, 6);
+	KeyBindsScreen_Show(Array_Elems(binds), binds, descs, "Normal controls");
+}
+
+
+/*########################################################################################################################*
+*--------------------------------------------------HacksBindingsScreen----------------------------------------------------*
+*#########################################################################################################################*/
+void HacksBindingsScreen_Show(void) {
+	static const cc_uint8 binds[]    = { BIND_SPEED, BIND_NOCLIP, BIND_HALF_SPEED, BIND_ZOOM_SCROLL, BIND_FLY, BIND_FLY_UP, BIND_FLY_DOWN, BIND_THIRD_PERSON };
+	static const char* const descs[] = { "Speed", "Noclip", "Half speed", "Scroll zoom", "Fly", "Fly up", "Fly down", "Third person" };
+	
+	KeyBindsScreen_Reset(Menu_SwitchBindsNormal, Menu_SwitchBindsOther, 260);
+	KeyBindsScreen_SetLayout(-40, 10, 4);
+	KeyBindsScreen_Show(Array_Elems(binds), binds, descs, "Hacks controls");
+}
+
+
+/*########################################################################################################################*
+*--------------------------------------------------OtherBindingsScreen----------------------------------------------------*
+*#########################################################################################################################*/
+void OtherBindingsScreen_Show(void) {
+	static const cc_uint8 binds[]     = { BIND_EXT_INPUT, BIND_HIDE_FPS, BIND_HIDE_GUI, BIND_HOTBAR_SWITCH, BIND_DROP_BLOCK,BIND_SCREENSHOT, BIND_FULLSCREEN, BIND_AXIS_LINES, BIND_AUTOROTATE, BIND_SMOOTH_CAMERA, BIND_IDOVERLAY, BIND_BREAK_LIQUIDS };
+	static const char* const descs[]  = { "Show ext input", "Hide FPS", "Hide gui", "Hotbar switching", "Drop block", "Screenshot", "Fullscreen", "Show axis lines", "Auto-rotate", "Smooth camera", "ID overlay", "Breakable liquids" };
+	
+	KeyBindsScreen_Reset(Menu_SwitchBindsHacks, Menu_SwitchBindsMouse, 260);
+	KeyBindsScreen_SetLayout(-140, 10, 6);
+	KeyBindsScreen_Show(Array_Elems(binds), binds, descs, "Other controls");
+}
+
+
+/*########################################################################################################################*
+*--------------------------------------------------MouseBindingsScreen----------------------------------------------------*
+*#########################################################################################################################*/
+void MouseBindingsScreen_Show(void) {
+	static const cc_uint8 binds[]    = { BIND_DELETE_BLOCK, BIND_PICK_BLOCK, BIND_PLACE_BLOCK, BIND_LOOK_UP, BIND_LOOK_DOWN, BIND_LOOK_LEFT, BIND_LOOK_RIGHT };
+	static const char* const descs[] = { "Delete block", "Pick block", "Place block", "Look Up", "Look Down", "Look Left", "Look Right" };
+
+	KeyBindsScreen_Reset(Menu_SwitchBindsOther, Menu_SwitchBindsHotbar, 260);
+	KeyBindsScreen_SetLayout(-140, 10, 3);
+	KeyBindsScreen.msgText = "&ePress escape to reset the binding";
+	KeyBindsScreen_Show(Array_Elems(binds), binds, descs, "Mouse key bindings");
+}
+
+
+/*########################################################################################################################*
+*-------------------------------------------------HotbarBindingsScreen----------------------------------------------------*
+*#########################################################################################################################*/
+void HotbarBindingsScreen_Show(void) {
+	static const cc_uint8 binds[] = { BIND_HOTBAR_1,BIND_HOTBAR_2,BIND_HOTBAR_3, BIND_HOTBAR_4,BIND_HOTBAR_5,BIND_HOTBAR_6, BIND_HOTBAR_7,BIND_HOTBAR_8,BIND_HOTBAR_9,
+										BIND_HOTBAR_LEFT, BIND_HOTBAR_RIGHT };
+	static const char* const descs[] = { "Slot #1","Slot #2","Slot #3", "Slot #4","Slot #5","Slot #6", "Slot #7","Slot #8","Slot #9", "Slot left","Slot right" };
+
+	KeyBindsScreen_Reset(Menu_SwitchBindsMouse, NULL, 260);
+	KeyBindsScreen_SetLayout(-140, 10, 6);
+	KeyBindsScreen_Show(Array_Elems(binds), binds, descs, "Hotbar controls");
+}
+
+
+
+/*########################################################################################################################*
+*--------------------------------------------------MenuInputOverlay-------------------------------------------------------*
+*#########################################################################################################################*/
+static struct MenuInputOverlay {
+	Screen_Body
+	cc_bool screenMode;
+	struct FontDesc textFont;
+	struct ButtonWidget ok, Default;
+	struct TextInputWidget input;
+	struct MenuInputDesc* desc;
+	MenuInputDone onDone;
+	cc_string value; char valueBuffer[STRING_SIZE];
+} MenuInputOverlay;
+
+static struct Widget* menuInput_widgets[2 + 1];
+
+static void MenuInputOverlay_Close(struct MenuInputOverlay* s, cc_bool valid) {
+	Gui_Remove((struct Screen*)&MenuInputOverlay);
+	s->onDone(&s->input.base.text, valid);
+}
+
+static void MenuInputOverlay_EnterInput(struct MenuInputOverlay* s) {
+	cc_bool valid = s->desc->VTABLE->IsValidValue(s->desc, &s->input.base.text);
+	MenuInputOverlay_Close(s, valid);
+}
+
+static int MenuInputOverlay_KeyPress(void* screen, char keyChar) {
+	struct MenuInputOverlay* s = (struct MenuInputOverlay*)screen;
+	InputWidget_Append(&s->input.base, keyChar);
+	return true;
+}
+
+static int MenuInputOverlay_TextChanged(void* screen, const cc_string* str) {
+	struct MenuInputOverlay* s = (struct MenuInputOverlay*)screen;
+	InputWidget_SetText(&s->input.base, str);
+	return true;
+}
+
+static int MenuInputOverlay_KeyDown(void* screen, int key) {
+	struct MenuInputOverlay* s = (struct MenuInputOverlay*)screen;
+	if (Elem_HandlesKeyDown(&s->input.base, key)) return true;
+
+	if (Input_IsEnterButton(key)) {
+		MenuInputOverlay_EnterInput(s); return true;
+	}
+	return Menu_InputDown(screen, key);
+}
+
+static int MenuInputOverlay_PointerDown(void* screen, int id, int x, int y) {
+	struct MenuInputOverlay* s = (struct MenuInputOverlay*)screen;
+	return Screen_DoPointerDown(screen, id, x, y) >= 0 || s->screenMode;
+}
+
+static int MenuInputOverlay_PointerMove(void* screen, int id, int x, int y) {
+	struct MenuInputOverlay* s = (struct MenuInputOverlay*)screen;
+	return Menu_DoPointerMove(screen, id, x, y) >= 0 || s->screenMode;
+}
+
+static void MenuInputOverlay_OK(void* screen, void* widget) {
+	struct MenuInputOverlay* s = (struct MenuInputOverlay*)screen;
+	MenuInputOverlay_EnterInput(s);
+}
+
+static void MenuInputOverlay_Default(void* screen, void* widget) {
+	cc_string value; char valueBuffer[STRING_SIZE];
+	struct MenuInputOverlay* s = (struct MenuInputOverlay*)screen;
+
+	String_InitArray(value, valueBuffer);
+	s->desc->VTABLE->GetDefault(s->desc, &value);
+	InputWidget_SetText(&s->input.base, &value);
+}
+
+static void MenuInputOverlay_Init(void* screen) {
+	struct MenuInputOverlay* s = (struct MenuInputOverlay*)screen;
+	s->widgets     = menuInput_widgets;
+	s->numWidgets  = 0;
+	s->maxWidgets  = Array_Elems(menuInput_widgets);
+
+	ButtonWidget_Add(s,    &s->ok, Input_TouchMode ? 200 : 40, MenuInputOverlay_OK);
+	ButtonWidget_Add(s,    &s->Default,              200, MenuInputOverlay_Default);
+	TextInputWidget_Add(s, &s->input,                400, &s->value, s->desc);
+
+	if (s->desc->VTABLE == &IntInput_VTABLE) {
+		s->input.onscreenType = KEYBOARD_TYPE_INTEGER;
+	} else if (s->desc->VTABLE == &FloatInput_VTABLE) {
+		s->input.onscreenType = KEYBOARD_TYPE_NUMBER;
+	}
+
+	s->maxVertices = Screen_CalcDefaultMaxVertices(s);
+}
+
+static void MenuInputOverlay_Update(void* screen, float delta) {
+	struct MenuInputOverlay* s = (struct MenuInputOverlay*)screen;
+	s->input.base.caretAccumulator += delta;
+}
+
+static void MenuInputOverlay_Render(void* screen, float delta) {
+	struct MenuInputOverlay* s = (struct MenuInputOverlay*)screen;
+	if (s->screenMode) Menu_RenderBounds();
+
+	Screen_Render2Widgets(screen, delta);
+}
+
+static void MenuInputOverlay_Free(void* screen) {
+	OnscreenKeyboard_Close();
+}
+
+static void MenuInputOverlay_Layout(void* screen) {
+	struct MenuInputOverlay* s = (struct MenuInputOverlay*)screen;
+	if (!Input_TouchMode) {
+		Widget_SetLocation(&s->input,   ANCHOR_CENTRE, ANCHOR_CENTRE,    0, 110);
+		Widget_SetLocation(&s->ok,      ANCHOR_CENTRE, ANCHOR_CENTRE,  240, 110);
+		Widget_SetLocation(&s->Default, ANCHOR_CENTRE, ANCHOR_CENTRE,    0, 150);
+	} else if (Window_Main.SoftKeyboard == SOFT_KEYBOARD_SHIFT) {
+		Widget_SetLocation(&s->input,   ANCHOR_CENTRE, ANCHOR_MAX,       0,  65);
+		Widget_SetLocation(&s->ok,      ANCHOR_CENTRE, ANCHOR_MAX,     120,  25);
+		Widget_SetLocation(&s->Default, ANCHOR_CENTRE, ANCHOR_MAX,    -120,  25);
+	} else {
+		Widget_SetLocation(&s->input,   ANCHOR_CENTRE, ANCHOR_CENTRE,    0, 110);
+		Widget_SetLocation(&s->ok,      ANCHOR_CENTRE, ANCHOR_CENTRE,  120, 150);
+		Widget_SetLocation(&s->Default, ANCHOR_CENTRE, ANCHOR_CENTRE, -120, 150);
+	}
+}
+
+static void MenuInputOverlay_ContextLost(void* screen) {
+	struct MenuInputOverlay* s = (struct MenuInputOverlay*)screen;
+	Font_Free(&s->textFont);
+	Screen_ContextLost(s);
+}
+
+static void MenuInputOverlay_ContextRecreated(void* screen) {
+	struct MenuInputOverlay* s = (struct MenuInputOverlay*)screen;
+	struct FontDesc font;
+	Gui_MakeTitleFont(&font);
+	Gui_MakeBodyFont(&s->textFont);
+	Screen_UpdateVb(s);
+
+	TextInputWidget_SetFont(&s->input, &s->textFont);
+	ButtonWidget_SetConst(&s->ok,      "OK",            &font);
+	ButtonWidget_SetConst(&s->Default, "Default value", &font);
+	Font_Free(&font);
+}
+
+static const struct ScreenVTABLE MenuInputOverlay_VTABLE = {
+	MenuInputOverlay_Init,        MenuInputOverlay_Update, MenuInputOverlay_Free,
+	MenuInputOverlay_Render,      Screen_BuildMesh,
+	MenuInputOverlay_KeyDown,     Screen_InputUp,   MenuInputOverlay_KeyPress,    MenuInputOverlay_TextChanged,
+	MenuInputOverlay_PointerDown, Screen_PointerUp, MenuInputOverlay_PointerMove, Screen_TMouseScroll,
+	MenuInputOverlay_Layout,      MenuInputOverlay_ContextLost, MenuInputOverlay_ContextRecreated
+};
+void MenuInputOverlay_Show(struct MenuInputDesc* desc, const cc_string* value, MenuInputDone onDone, cc_bool screenMode) {
+	struct MenuInputOverlay* s = &MenuInputOverlay;
+	s->grabsInput = true;
+	s->closable   = true;
+	s->desc       = desc;
+	s->onDone     = onDone;
+	s->screenMode = screenMode;
+	s->VTABLE     = &MenuInputOverlay_VTABLE;
+
+	String_InitArray(s->value, s->valueBuffer);
+	String_Copy(&s->value, value);
+	Gui_Add((struct Screen*)s, GUI_PRIORITY_MENUINPUT);
+}
+
+
+/*########################################################################################################################*
+*--------------------------------------------------MenuOptionsScreen------------------------------------------------------*
+*#########################################################################################################################*/
+struct MenuOptionsScreen;
+typedef void (*InitMenuOptions)(struct MenuOptionsScreen* s);
+#define MENUOPTS_MAX_OPTS 11
+static void MenuOptionsScreen_Layout(void* screen);
+
+static struct MenuOptionsScreen {
+	Screen_Body
+	const char* descriptions[MENUOPTS_MAX_OPTS + 1];
+	struct ButtonWidget* activeBtn;
+	InitMenuOptions DoInit, DoRecreateExtra, OnHacksChanged, OnLightingModeServerChanged;
+	int numButtons;
+	struct FontDesc titleFont, textFont;
+	struct TextGroupWidget extHelp;
+	struct Texture extHelpTextures[5]; /* max lines is 5 */
+	struct ButtonWidget buttons[MENUOPTS_MAX_OPTS], done;
+	const char* extHelpDesc;
+} MenuOptionsScreen_Instance;
+
+static struct MenuInputDesc menuOpts_descs[MENUOPTS_MAX_OPTS];
+static struct Widget* menuOpts_widgets[MENUOPTS_MAX_OPTS + 1];
+
+static void Menu_GetBool(cc_string* raw, cc_bool v) {
+	String_AppendConst(raw, v ? "ON" : "OFF");
+}
+static cc_bool Menu_SetBool(const cc_string* raw, const char* key) {
+	cc_bool isOn = String_CaselessEqualsConst(raw, "ON");
+	Options_SetBool(key, isOn); 
+	return isOn;
+}
+
+static void MenuOptionsScreen_GetFPS(cc_string* raw) {
+	String_AppendConst(raw, FpsLimit_Names[Game_FpsLimit]);
+}
+static void MenuOptionsScreen_SetFPS(const cc_string* v) {
+	int method = Utils_ParseEnum(v, FPS_LIMIT_VSYNC, FpsLimit_Names, Array_Elems(FpsLimit_Names));
+	Options_Set(OPT_FPS_LIMIT, v);
+	Game_SetFpsLimit(method);
+}
+
+static void MenuOptionsScreen_Update(struct MenuOptionsScreen* s, struct ButtonWidget* btn) {
+	cc_string title; char titleBuffer[STRING_SIZE];
+	String_InitArray(title, titleBuffer);
+
+	String_AppendConst(&title, btn->optName);
+	if (btn->GetValue) {
+		String_AppendConst(&title, ": ");
+		btn->GetValue(&title);
+	}	
+	ButtonWidget_Set(btn, &title, &s->titleFont);
+}
+
+CC_NOINLINE static void MenuOptionsScreen_Set(struct MenuOptionsScreen* s, 
+											struct ButtonWidget* btn, const cc_string* text) {
+	btn->SetValue(text);
+	MenuOptionsScreen_Update(s, btn);
+}
+
+CC_NOINLINE static void MenuOptionsScreen_FreeExtHelp(struct MenuOptionsScreen* s) {
+	Elem_Free(&s->extHelp);
+	s->extHelp.lines = 0;
+}
+
+static void MenuOptionsScreen_LayoutExtHelp(struct MenuOptionsScreen* s) {
+	Widget_SetLocation(&s->extHelp, ANCHOR_MIN, ANCHOR_CENTRE_MIN, 0, 100);
+	/* If use centre align above, then each line in extended help gets */
+	/* centered aligned separately - which is not the desired behaviour. */
+	s->extHelp.xOffset = Window_UI.Width / 2 - s->extHelp.width / 2;
+	Widget_Layout(&s->extHelp);
+}
+
+static cc_string MenuOptionsScreen_GetDesc(int i) {
+	const char* desc = MenuOptionsScreen_Instance.extHelpDesc;
+	cc_string descRaw, descLines[5];
+
+	descRaw = String_FromReadonly(desc);
+	String_UNSAFE_Split(&descRaw, '\n', descLines, Array_Elems(descLines));
+	return descLines[i];
+}
+
+static void MenuOptionsScreen_SelectExtHelp(struct MenuOptionsScreen* s, int idx) {
+	const char* desc;
+	cc_string descRaw, descLines[5];
+
+	MenuOptionsScreen_FreeExtHelp(s);
+	if (s->activeBtn) return;
+	desc = s->descriptions[idx];
+	if (!desc) return;
+
+	if (!s->widgets[idx]) return;
+	if (s->widgets[idx]->flags & WIDGET_FLAG_DISABLED) return;
+
+	descRaw          = String_FromReadonly(desc);
+	s->extHelp.lines = String_UNSAFE_Split(&descRaw, '\n', descLines, Array_Elems(descLines));
+	
+	s->extHelpDesc = desc;
+	TextGroupWidget_RedrawAll(&s->extHelp);
+	MenuOptionsScreen_LayoutExtHelp(s);
+}
+
+static void MenuOptionsScreen_OnDone(const cc_string* value, cc_bool valid) {
+	struct MenuOptionsScreen* s = &MenuOptionsScreen_Instance;
+	if (valid) {
+		MenuOptionsScreen_Set(s, s->activeBtn, value);
+		/* Marking screen as dirty fixes changed option widget appearing wrong */
+		/*  for a few frames (e.g. Chatlines options changed from '12' to '1') */
+		s->dirty = true;
+	}
+
+	if (s->selectedI >= 0) MenuOptionsScreen_SelectExtHelp(s, s->selectedI);
+	s->activeBtn = NULL;
+}
+
+static int MenuOptionsScreen_PointerMove(void* screen, int id, int x, int y) {
+	struct MenuOptionsScreen* s = (struct MenuOptionsScreen*)screen;
+	int i = Menu_DoPointerMove(s, id, x, y);
+	if (i == -1 || i == s->selectedI) return true;
+
+	s->selectedI = i;
+	if (!s->activeBtn) MenuOptionsScreen_SelectExtHelp(s, i);
+	return true;
+}
+
+static void MenuOptionsScreen_AddButtons(struct MenuOptionsScreen* s, const struct MenuOptionDesc* btns, int count, Widget_LeftClick backClick) {
+	struct ButtonWidget* btn;
+	int i;
+	
+	for (i = 0; i < count; i++) {
+		btn = &s->buttons[i];
+		ButtonWidget_Add(s, btn,  300, btns[i].OnClick);
+		Widget_SetLocation(btn, ANCHOR_CENTRE, ANCHOR_CENTRE, btns[i].dir * 160, btns[i].y);
+
+		btn->optName  = btns[i].name;
+		btn->GetValue = btns[i].GetValue;
+		btn->SetValue = btns[i].SetValue;
+		btn->meta.ptr = &menuOpts_descs[i];
+		s->widgets[i] = (struct Widget*)btn;
+	}
+	s->numButtons = count;
+	ButtonWidget_Add(s, &s->done, 400, backClick);
+}
+
+static void MenuOptionsScreen_Bool(void* screen, void* widget) {
+	cc_string value; char valueBuffer[STRING_SIZE];
+	struct MenuOptionsScreen* s = (struct MenuOptionsScreen*)screen;
+	struct ButtonWidget* btn    = (struct ButtonWidget*)widget;
+	cc_bool isOn;
+
+	String_InitArray(value, valueBuffer);
+	btn->GetValue(&value);
+
+	isOn  = String_CaselessEqualsConst(&value, "ON");
+	value = String_FromReadonly(isOn ? "OFF" : "ON");
+	MenuOptionsScreen_Set(s, btn, &value);
+}
+
+static void MenuOptionsScreen_Enum(void* screen, void* widget) {
+	cc_string value; char valueBuffer[STRING_SIZE];
+	struct MenuOptionsScreen* s = (struct MenuOptionsScreen*)screen;
+	struct ButtonWidget* btn    = (struct ButtonWidget*)widget;
+	struct MenuInputDesc* desc;
+	const char* const* names;
+	int raw, count;
+	
+	String_InitArray(value, valueBuffer);
+	btn->GetValue(&value);
+
+	desc  = (struct MenuInputDesc*)btn->meta.ptr;
+	names = desc->meta.e.Names;
+	count = desc->meta.e.Count;	
+
+	raw   = (Utils_ParseEnum(&value, 0, names, count) + 1) % count;
+	value = String_FromReadonly(names[raw]);
+	MenuOptionsScreen_Set(s, btn, &value);
+}
+
+static void MenuInputOverlay_CheckStillValid(struct MenuOptionsScreen* s) {
+	if (!s->activeBtn) return;
+	
+	if (s->activeBtn->flags & WIDGET_FLAG_DISABLED) {
+		/* source button is disabled now, so close open input overlay */
+		MenuInputOverlay_Close(&MenuInputOverlay, false);
+	}
+}
+
+static void MenuOptionsScreen_Input(void* screen, void* widget) {
+	cc_string value; char valueBuffer[STRING_SIZE];
+	struct MenuOptionsScreen* s = (struct MenuOptionsScreen*)screen;
+	struct ButtonWidget* btn    = (struct ButtonWidget*)widget;
+	struct MenuInputDesc* desc;
+
+	MenuOptionsScreen_FreeExtHelp(s);
+	s->activeBtn = btn;
+
+	String_InitArray(value, valueBuffer);
+	btn->GetValue(&value);
+	desc = (struct MenuInputDesc*)btn->meta.ptr;
+	MenuInputOverlay_Show(desc, &value, MenuOptionsScreen_OnDone, Gui_TouchUI);
+}
+
+static void MenuOptionsScreen_OnHacksChanged(void* screen) {
+	struct MenuOptionsScreen* s = (struct MenuOptionsScreen*)screen;
+	if (s->OnHacksChanged) s->OnHacksChanged(s);
+	s->dirty = true;
+}
+static void MenuOptionsScreen_OnLightingModeServerChanged(void* screen, cc_uint8 oldMode, cc_bool fromServer) {
+	struct MenuOptionsScreen* s = (struct MenuOptionsScreen*)screen;
+	/* This event only actually matters if it's from the server */
+	if (fromServer) {
+		if (s->OnLightingModeServerChanged) s->OnLightingModeServerChanged(s);
+		s->dirty = true;
+	}
+}
+
+static void MenuOptionsScreen_Init(void* screen) {
+	struct MenuOptionsScreen* s = (struct MenuOptionsScreen*)screen;
+	int i;
+
+	s->widgets    = menuOpts_widgets;
+	s->numWidgets = 0;
+	s->maxWidgets = MENUOPTS_MAX_OPTS + 1; /* always have back button */
+
+	/* The various menu options screens might have different number of widgets */
+	for (i = 0; i < MENUOPTS_MAX_OPTS; i++) { 
+		s->widgets[i]      = NULL;
+		s->descriptions[i] = NULL;
+	}
+
+	s->activeBtn   = NULL;
+	s->selectedI   = -1;
+	s->DoInit(s);
+
+	TextGroupWidget_Create(&s->extHelp, 5, s->extHelpTextures, MenuOptionsScreen_GetDesc);
+	s->extHelp.lines = 0;
+	Event_Register_(&UserEvents.HackPermsChanged, screen, MenuOptionsScreen_OnHacksChanged);
+	Event_Register_(&WorldEvents.LightingModeChanged, screen, MenuOptionsScreen_OnLightingModeServerChanged);
+	
+	s->maxVertices = Screen_CalcDefaultMaxVertices(s);
+}
+	
+#define EXTHELP_PAD 5 /* padding around extended help box */
+static void MenuOptionsScreen_Render(void* screen, float delta) {
+	struct MenuOptionsScreen* s = (struct MenuOptionsScreen*)screen;
+	struct TextGroupWidget* w;
+	PackedCol tableColor = PackedCol_Make(20, 20, 20, 200);
+
+	MenuScreen_Render2(s, delta);
+	if (!s->extHelp.lines) return;
+
+	w = &s->extHelp;
+	Gfx_Draw2DFlat(w->x - EXTHELP_PAD, w->y - EXTHELP_PAD, 
+		w->width + EXTHELP_PAD * 2, w->height + EXTHELP_PAD * 2, tableColor);
+
+	Elem_Render(&s->extHelp, delta);
+}
+
+static void MenuOptionsScreen_Free(void* screen) {
+	struct MenuOptionsScreen* s = (struct MenuOptionsScreen*)screen;
+	Event_Unregister_(&UserEvents.HackPermsChanged, screen, MenuOptionsScreen_OnHacksChanged);
+	Event_Unregister_(&WorldEvents.LightingModeChanged, screen, MenuOptionsScreen_OnLightingModeServerChanged);
+	Gui_RemoveCore((struct Screen*)&MenuInputOverlay);
+}
+
+static void MenuOptionsScreen_Layout(void* screen) {
+	struct MenuOptionsScreen* s = (struct MenuOptionsScreen*)screen;
+	Screen_Layout(s);
+	Menu_LayoutBack(&s->done);
+	MenuOptionsScreen_LayoutExtHelp(s);
+}
+
+static void MenuOptionsScreen_ContextLost(void* screen) {
+	struct MenuOptionsScreen* s = (struct MenuOptionsScreen*)screen;
+	Font_Free(&s->titleFont);
+	Font_Free(&s->textFont);
+	Screen_ContextLost(s);
+	Elem_Free(&s->extHelp);
+}
+
+static void MenuOptionsScreen_ContextRecreated(void* screen) {
+	struct MenuOptionsScreen* s = (struct MenuOptionsScreen*)screen;
+	int i;
+	Gui_MakeTitleFont(&s->titleFont);
+	Gui_MakeBodyFont(&s->textFont);
+	Screen_UpdateVb(screen);
+
+	for (i = 0; i < s->numButtons; i++) 
+	{ 
+		if (s->widgets[i]) MenuOptionsScreen_Update(s, &s->buttons[i]); 
+	}
+
+	ButtonWidget_SetConst(&s->done, "Done", &s->titleFont);
+	if (s->DoRecreateExtra) s->DoRecreateExtra(s);
+	TextGroupWidget_SetFont(&s->extHelp, &s->textFont);
+	TextGroupWidget_RedrawAll(&s->extHelp); /* TODO: SetFont should redrawall implicitly */
+}
+
+static const struct ScreenVTABLE MenuOptionsScreen_VTABLE = {
+	MenuOptionsScreen_Init,   Screen_NullUpdate, MenuOptionsScreen_Free, 
+	MenuOptionsScreen_Render, Screen_BuildMesh,
+	Menu_InputDown,           Screen_InputUp,    Screen_TKeyPress, Screen_TText,
+	Menu_PointerDown,         Screen_PointerUp,  MenuOptionsScreen_PointerMove, Screen_TMouseScroll,
+	MenuOptionsScreen_Layout, MenuOptionsScreen_ContextLost, MenuOptionsScreen_ContextRecreated
+};
+void MenuOptionsScreen_Show(InitMenuOptions init) {
+	struct MenuOptionsScreen* s = &MenuOptionsScreen_Instance;
+	s->grabsInput = true;
+	s->closable   = true;
+	s->VTABLE     = &MenuOptionsScreen_VTABLE;
+
+	s->DoInit          = init;
+	s->DoRecreateExtra = NULL;
+	s->OnHacksChanged  = NULL;
+	Gui_Add((struct Screen*)s, GUI_PRIORITY_MENU);
+}
+
+
+/*########################################################################################################################*
+*---------------------------------------------------ClassicOptionsScreen--------------------------------------------------*
+*#########################################################################################################################*/
+enum ViewDist { VIEW_TINY, VIEW_SHORT, VIEW_NORMAL, VIEW_FAR, VIEW_COUNT };
+static const char* const viewDistNames[VIEW_COUNT] = { "TINY", "SHORT", "NORMAL", "FAR" };
+
+static void ClassicOptionsScreen_GetMusic(cc_string* v) { Menu_GetBool(v, Audio_MusicVolume > 0); }
+static void ClassicOptionsScreen_SetMusic(const cc_string* v) {
+	Audio_SetMusic(String_CaselessEqualsConst(v, "ON") ? 100 : 0);
+	Options_SetInt(OPT_MUSIC_VOLUME, Audio_MusicVolume);
+}
+
+static void ClassicOptionsScreen_GetInvert(cc_string* v) { Menu_GetBool(v, Camera.Invert); }
+static void ClassicOptionsScreen_SetInvert(const cc_string* v) { Camera.Invert = Menu_SetBool(v, OPT_INVERT_MOUSE); }
+
+static void ClassicOptionsScreen_GetViewDist(cc_string* v) {
+	if (Game_ViewDistance >= 512) {
+		String_AppendConst(v, viewDistNames[VIEW_FAR]);
+	} else if (Game_ViewDistance >= 128) {
+		String_AppendConst(v, viewDistNames[VIEW_NORMAL]);
+	} else if (Game_ViewDistance >= 32) {
+		String_AppendConst(v, viewDistNames[VIEW_SHORT]);
+	} else {
+		String_AppendConst(v, viewDistNames[VIEW_TINY]);
+	}
+}
+static void ClassicOptionsScreen_SetViewDist(const cc_string* v) {
+	int raw  = Utils_ParseEnum(v, 0, viewDistNames, VIEW_COUNT);
+	int dist = raw == VIEW_FAR ? 512 : (raw == VIEW_NORMAL ? 128 : (raw == VIEW_SHORT ? 32 : 8));
+	Game_UserSetViewDistance(dist);
+}
+
+static void ClassicOptionsScreen_GetAnaglyph(cc_string* v) { Menu_GetBool(v, Game_Anaglyph3D); }
+static void ClassicOptionsScreen_SetAnaglyph(const cc_string* v) {
+	Game_Anaglyph3D = Menu_SetBool(v, OPT_ANAGLYPH3D);
+}
+
+static void ClassicOptionsScreen_GetSounds(cc_string* v) { Menu_GetBool(v, Audio_SoundsVolume > 0); }
+static void ClassicOptionsScreen_SetSounds(const cc_string* v) {
+	Audio_SetSounds(String_CaselessEqualsConst(v, "ON") ? 100 : 0);
+	Options_SetInt(OPT_SOUND_VOLUME, Audio_SoundsVolume);
+}
+
+static void ClassicOptionsScreen_GetShowFPS(cc_string* v) { Menu_GetBool(v, Gui.ShowFPS); }
+static void ClassicOptionsScreen_SetShowFPS(const cc_string* v) { Gui.ShowFPS = Menu_SetBool(v, OPT_SHOW_FPS); }
+
+static void ClassicOptionsScreen_GetViewBob(cc_string* v) { Menu_GetBool(v, Game_ViewBobbing); }
+static void ClassicOptionsScreen_SetViewBob(const cc_string* v) { Game_ViewBobbing = Menu_SetBool(v, OPT_VIEW_BOBBING); }
+
+static void ClassicOptionsScreen_GetHacks(cc_string* v) { Menu_GetBool(v, Entities.CurPlayer->Hacks.Enabled); }
+static void ClassicOptionsScreen_SetHacks(const cc_string* v) {
+	Entities.CurPlayer->Hacks.Enabled = Menu_SetBool(v, OPT_HACKS_ENABLED);
+	HacksComp_Update(&Entities.CurPlayer->Hacks);
+}
+
+static void ClassicOptionsScreen_RecreateExtra(struct MenuOptionsScreen* s) {
+	ButtonWidget_SetConst(&s->buttons[9], "Controls...", &s->titleFont);
+}
+
+static void ClassicOptionsScreen_InitWidgets(struct MenuOptionsScreen* s) {
+	static const struct MenuOptionDesc buttons[] = {
+		{ -1, -150, "Music",           MenuOptionsScreen_Bool,
+			ClassicOptionsScreen_GetMusic,    ClassicOptionsScreen_SetMusic },
+		{ -1, -100, "Invert mouse",    MenuOptionsScreen_Bool,
+			ClassicOptionsScreen_GetInvert,   ClassicOptionsScreen_SetInvert },
+		{ -1,  -50, "Render distance", MenuOptionsScreen_Enum,
+			ClassicOptionsScreen_GetViewDist, ClassicOptionsScreen_SetViewDist },
+		{ -1,    0, "3D anaglyph",     MenuOptionsScreen_Bool,
+			ClassicOptionsScreen_GetAnaglyph, ClassicOptionsScreen_SetAnaglyph },
+
+		{ 1, -150, "Sound",         MenuOptionsScreen_Bool,
+			ClassicOptionsScreen_GetSounds,  ClassicOptionsScreen_SetSounds },
+		{ 1, -100, "Show FPS",      MenuOptionsScreen_Bool,
+			ClassicOptionsScreen_GetShowFPS, ClassicOptionsScreen_SetShowFPS },
+		{ 1,  -50, "View bobbing",  MenuOptionsScreen_Bool,
+			ClassicOptionsScreen_GetViewBob, ClassicOptionsScreen_SetViewBob },
+		{ 1,    0, "FPS mode",      MenuOptionsScreen_Enum,
+			MenuOptionsScreen_GetFPS,        MenuOptionsScreen_SetFPS },
+		{ 0,   60, "Hacks enabled", MenuOptionsScreen_Bool,
+			ClassicOptionsScreen_GetHacks,   ClassicOptionsScreen_SetHacks }
+	};
+	s->DoRecreateExtra = ClassicOptionsScreen_RecreateExtra;
+
+	MenuOptionsScreen_AddButtons(s, buttons, Array_Elems(buttons), Menu_SwitchPause);
+	ButtonWidget_Add(s, &s->buttons[9], 400, Menu_SwitchBindsClassic);
+	Widget_SetLocation(&s->buttons[9],  ANCHOR_CENTRE, ANCHOR_MAX, 0, 95);
+
+	/* Disable certain options */
+	if (!Server.IsSinglePlayer) Menu_Remove(s, 3);
+	if (!Game_ClassicHacks)     Menu_Remove(s, 8);
+}
+
+void ClassicOptionsScreen_Show(void) {
+	MenuInput_Enum(menuOpts_descs[2], viewDistNames,  VIEW_COUNT);
+	MenuInput_Enum(menuOpts_descs[7], FpsLimit_Names, FPS_LIMIT_COUNT);
+
+	MenuOptionsScreen_Show(ClassicOptionsScreen_InitWidgets);
+}
+
+
+/*########################################################################################################################*
+*----------------------------------------------------EnvSettingsScreen----------------------------------------------------*
+*#########################################################################################################################*/
+static void EnvSettingsScreen_GetCloudsColor(cc_string* v) { PackedCol_ToHex(v, Env.CloudsCol); }
+static void EnvSettingsScreen_SetCloudsColor(const cc_string* v) { Env_SetCloudsCol(Menu_HexCol(v)); }
+
+static void EnvSettingsScreen_GetSkyColor(cc_string* v) { PackedCol_ToHex(v, Env.SkyCol); }
+static void EnvSettingsScreen_SetSkyColor(const cc_string* v) { Env_SetSkyCol(Menu_HexCol(v)); }
+
+static void EnvSettingsScreen_GetFogColor(cc_string* v) { PackedCol_ToHex(v, Env.FogCol); }
+static void EnvSettingsScreen_SetFogColor(const cc_string* v) { Env_SetFogCol(Menu_HexCol(v)); }
+
+static void EnvSettingsScreen_GetCloudsSpeed(cc_string* v) { String_AppendFloat(v, Env.CloudsSpeed, 2); }
+static void EnvSettingsScreen_SetCloudsSpeed(const cc_string* v) { Env_SetCloudsSpeed(Menu_Float(v)); }
+
+static void EnvSettingsScreen_GetCloudsHeight(cc_string* v) { String_AppendInt(v, Env.CloudsHeight); }
+static void EnvSettingsScreen_SetCloudsHeight(const cc_string* v) { Env_SetCloudsHeight(Menu_Int(v)); }
+
+static void EnvSettingsScreen_GetSunColor(cc_string* v) { PackedCol_ToHex(v, Env.SunCol); }
+static void EnvSettingsScreen_SetSunColor(const cc_string* v) { Env_SetSunCol(Menu_HexCol(v)); }
+
+static void EnvSettingsScreen_GetShadowColor(cc_string* v) { PackedCol_ToHex(v, Env.ShadowCol); }
+static void EnvSettingsScreen_SetShadowColor(const cc_string* v) { Env_SetShadowCol(Menu_HexCol(v)); }
+
+static void EnvSettingsScreen_GetWeather(cc_string* v) { String_AppendConst(v, Weather_Names[Env.Weather]); }
+static void EnvSettingsScreen_SetWeather(const cc_string* v) {
+	int raw = Utils_ParseEnum(v, 0, Weather_Names, Array_Elems(Weather_Names));
+	Env_SetWeather(raw); 
+}
+
+static void EnvSettingsScreen_GetWeatherSpeed(cc_string* v) { String_AppendFloat(v, Env.WeatherSpeed, 2); }
+static void EnvSettingsScreen_SetWeatherSpeed(const cc_string* v) { Env_SetWeatherSpeed(Menu_Float(v)); }
+
+static void EnvSettingsScreen_GetEdgeHeight(cc_string* v) { String_AppendInt(v, Env.EdgeHeight); }
+static void EnvSettingsScreen_SetEdgeHeight(const cc_string* v) { Env_SetEdgeHeight(Menu_Int(v)); }
+
+static void EnvSettingsScreen_InitWidgets(struct MenuOptionsScreen* s) {
+	static const struct MenuOptionDesc buttons[] = {
+		{ -1, -150, "Clouds color",  MenuOptionsScreen_Input,
+			EnvSettingsScreen_GetCloudsColor,  EnvSettingsScreen_SetCloudsColor },
+		{ -1, -100, "Sky color",     MenuOptionsScreen_Input,
+			EnvSettingsScreen_GetSkyColor,     EnvSettingsScreen_SetSkyColor },
+		{ -1,  -50, "Fog color",     MenuOptionsScreen_Input,
+			EnvSettingsScreen_GetFogColor,     EnvSettingsScreen_SetFogColor },
+		{ -1,    0, "Clouds speed",  MenuOptionsScreen_Input,
+			EnvSettingsScreen_GetCloudsSpeed,  EnvSettingsScreen_SetCloudsSpeed },
+		{ -1,   50, "Clouds height", MenuOptionsScreen_Input,
+			EnvSettingsScreen_GetCloudsHeight, EnvSettingsScreen_SetCloudsHeight },
+
+		{ 1, -150, "Sunlight color",  MenuOptionsScreen_Input,
+			EnvSettingsScreen_GetSunColor,     EnvSettingsScreen_SetSunColor },
+		{ 1, -100, "Shadow color",    MenuOptionsScreen_Input,
+			EnvSettingsScreen_GetShadowColor,  EnvSettingsScreen_SetShadowColor },
+		{ 1,  -50, "Weather",         MenuOptionsScreen_Enum,
+			EnvSettingsScreen_GetWeather,      EnvSettingsScreen_SetWeather },
+		{ 1,    0, "Rain/Snow speed", MenuOptionsScreen_Input,
+			EnvSettingsScreen_GetWeatherSpeed, EnvSettingsScreen_SetWeatherSpeed },
+		{ 1,   50, "Water level",     MenuOptionsScreen_Input,
+			EnvSettingsScreen_GetEdgeHeight,   EnvSettingsScreen_SetEdgeHeight }
+	};
+	MenuOptionsScreen_AddButtons(s, buttons, Array_Elems(buttons), Menu_SwitchOptions);
+}
+
+void EnvSettingsScreen_Show(void) {
+	MenuInput_Hex(menuOpts_descs[0],   ENV_DEFAULT_CLOUDS_COLOR);
+	MenuInput_Hex(menuOpts_descs[1],   ENV_DEFAULT_SKY_COLOR);
+	MenuInput_Hex(menuOpts_descs[2],   ENV_DEFAULT_FOG_COLOR);
+	MenuInput_Float(menuOpts_descs[3],      0,  1000, 1);
+	MenuInput_Int(menuOpts_descs[4],   -10000, 10000, World.Height + 2);
+
+	MenuInput_Hex(menuOpts_descs[5],   ENV_DEFAULT_SUN_COLOR);
+	MenuInput_Hex(menuOpts_descs[6],   ENV_DEFAULT_SHADOW_COLOR);
+	MenuInput_Enum(menuOpts_descs[7],  Weather_Names, Array_Elems(Weather_Names));
+	MenuInput_Float(menuOpts_descs[8],  -100,  100, 1);
+	MenuInput_Int(menuOpts_descs[9],   -2048, 2048, World.Height / 2);
+
+	MenuOptionsScreen_Show(EnvSettingsScreen_InitWidgets);
+}
+
+
+/*########################################################################################################################*
+*--------------------------------------------------GraphicsOptionsScreen--------------------------------------------------*
+*#########################################################################################################################*/
+static void GraphicsOptionsScreen_CheckLightingModeAllowed(struct MenuOptionsScreen* s) {
+	Widget_SetDisabled(s->widgets[4], Lighting_ModeLockedByServer);
+}
+
+static void GraphicsOptionsScreen_GetViewDist(cc_string* v) { String_AppendInt(v, Game_ViewDistance); }
+static void GraphicsOptionsScreen_SetViewDist(const cc_string* v) { Game_UserSetViewDistance(Menu_Int(v)); }
+
+static void GraphicsOptionsScreen_GetSmooth(cc_string* v) { Menu_GetBool(v, Builder_SmoothLighting); }
+static void GraphicsOptionsScreen_SetSmooth(const cc_string* v) {
+	Builder_SmoothLighting = Menu_SetBool(v, OPT_SMOOTH_LIGHTING);
+	Builder_ApplyActive();
+	MapRenderer_Refresh();
+}
+
+static void GraphicsOptionsScreen_GetLighting(cc_string* v) { String_AppendConst(v, LightingMode_Names[Lighting_Mode]); }
+static void GraphicsOptionsScreen_SetLighting(const cc_string* v) {
+	cc_uint8 mode = Utils_ParseEnum(v, 0, LightingMode_Names, LIGHTING_MODE_COUNT);
+	Options_Set(OPT_LIGHTING_MODE, v);
+
+	Lighting_ModeSetByServer = false;
+	Lighting_SetMode(mode, false);
+}
+
+static void GraphicsOptionsScreen_GetCamera(cc_string* v) { Menu_GetBool(v, Camera.Smooth); }
+static void GraphicsOptionsScreen_SetCamera(const cc_string* v) { Camera.Smooth = Menu_SetBool(v, OPT_CAMERA_SMOOTH); }
+
+static void GraphicsOptionsScreen_GetNames(cc_string* v) { String_AppendConst(v, NameMode_Names[Entities.NamesMode]); }
+static void GraphicsOptionsScreen_SetNames(const cc_string* v) {
+	Entities.NamesMode = Utils_ParseEnum(v, 0, NameMode_Names, NAME_MODE_COUNT);
+	Options_Set(OPT_NAMES_MODE, v);
+}
+
+static void GraphicsOptionsScreen_GetShadows(cc_string* v) { String_AppendConst(v, ShadowMode_Names[Entities.ShadowsMode]); }
+static void GraphicsOptionsScreen_SetShadows(const cc_string* v) {
+	Entities.ShadowsMode = Utils_ParseEnum(v, 0, ShadowMode_Names, SHADOW_MODE_COUNT);
+	Options_Set(OPT_ENTITY_SHADOW, v);
+}
+
+static void GraphicsOptionsScreen_GetMipmaps(cc_string* v) { Menu_GetBool(v, Gfx.Mipmaps); }
+static void GraphicsOptionsScreen_SetMipmaps(const cc_string* v) {
+	Gfx.Mipmaps = Menu_SetBool(v, OPT_MIPMAPS);
+	TexturePack_ExtractCurrent(true);
+}
+
+static void GraphicsOptionsScreen_GetCameraMass(cc_string* v) { String_AppendFloat(v, Camera.Mass, 2); }
+static void GraphicsOptionsScreen_SetCameraMass(const cc_string* c) {
+	Camera.Mass = Menu_Float(c);
+	Options_Set(OPT_CAMERA_MASS, c);
+}
+
+static void GraphicsOptionsScreen_InitWidgets(struct MenuOptionsScreen* s) {
+	static const struct MenuOptionDesc buttons[] = {
+		{ -1, -150, "Camera Mass",       MenuOptionsScreen_Input,
+			GraphicsOptionsScreen_GetCameraMass, GraphicsOptionsScreen_SetCameraMass },
+		{ -1, -100, "FPS mode",          MenuOptionsScreen_Enum,
+			MenuOptionsScreen_GetFPS,          MenuOptionsScreen_SetFPS },
+		{ -1,  -50, "View distance",     MenuOptionsScreen_Input,
+			GraphicsOptionsScreen_GetViewDist,   GraphicsOptionsScreen_SetViewDist },
+		{ -1,    0, "Smooth lighting", MenuOptionsScreen_Bool,
+			GraphicsOptionsScreen_GetSmooth,     GraphicsOptionsScreen_SetSmooth },
+		{ -1,  50,  "Lighting mode", MenuOptionsScreen_Enum,
+			GraphicsOptionsScreen_GetLighting,   GraphicsOptionsScreen_SetLighting },
+		{ 1, -150, "Smooth camera", MenuOptionsScreen_Bool,
+			GraphicsOptionsScreen_GetCamera,   GraphicsOptionsScreen_SetCamera },
+		{ 1, -100, "Names",   MenuOptionsScreen_Enum,
+			GraphicsOptionsScreen_GetNames,   GraphicsOptionsScreen_SetNames },
+		{ 1,  -50, "Shadows", MenuOptionsScreen_Enum,
+			GraphicsOptionsScreen_GetShadows, GraphicsOptionsScreen_SetShadows },
+#ifdef CC_BUILD_N64
+		{ 1,    0,  "Filtering", MenuOptionsScreen_Bool,
+			GraphicsOptionsScreen_GetMipmaps, GraphicsOptionsScreen_SetMipmaps },
+#else
+		{ 1,    0,  "Mipmaps", MenuOptionsScreen_Bool,
+			GraphicsOptionsScreen_GetMipmaps, GraphicsOptionsScreen_SetMipmaps },
+#endif
+		{ 1,   50,  "Anaglyph 3D", MenuOptionsScreen_Bool,
+			ClassicOptionsScreen_GetAnaglyph, ClassicOptionsScreen_SetAnaglyph }
+	};
+
+	s->OnLightingModeServerChanged = GraphicsOptionsScreen_CheckLightingModeAllowed;
+
+	MenuOptionsScreen_AddButtons(s, buttons, Array_Elems(buttons), Menu_SwitchOptions);
+	GraphicsOptionsScreen_CheckLightingModeAllowed(s);
+
+	s->descriptions[0] = "&eChange the smoothness of the smooth camera.";
+	s->descriptions[1] = \
+		"&eVSync: &fNumber of frames rendered is at most the monitor's refresh rate.\n" \
+		"&e30/60/120/144 FPS: &fRenders 30/60/120/144 frames at most each second.\n" \
+		"&eNoLimit: &fRenders as many frames as possible each second.\n" \
+		"&cNoLimit is pointless - it wastefully renders frames that you don't even see!";
+	s->descriptions[3] = \
+		"&eSmooth lighting smooths lighting and adds a minor glow to bright blocks.\n" \
+		"&cNote: &eThis setting may reduce performance.";
+	s->descriptions[4] = \
+		"&eClassic: &fTwo levels of light, sun and shadow.\n" \
+		"    Good for performance.\n" \
+		"&eFancy: &fBright blocks cast a much wider range of light\n" \
+		"    May heavily reduce performance.\n" \
+		"&cNote: &eIn multiplayer, this option may be changed or locked by the server.";
+	s->descriptions[6] = \
+		"&eNone: &fNo names of players are drawn.\n" \
+		"&eHovered: &fName of the targeted player is drawn see-through.\n" \
+		"&eAll: &fNames of all other players are drawn normally.\n" \
+		"&eAllHovered: &fAll names of players are drawn see-through.\n" \
+		"&eAllUnscaled: &fAll names of players are drawn see-through without scaling.";
+	s->descriptions[7] = \
+		"&eNone: &fNo entity shadows are drawn.\n" \
+		"&eSnapToBlock: &fA square shadow is shown on block you are directly above.\n" \
+		"&eCircle: &fA circular shadow is shown across the blocks you are above.\n" \
+		"&eCircleAll: &fA circular shadow is shown underneath all entities.";
+}
+
+void GraphicsOptionsScreen_Show(void) {
+	MenuInput_Float(menuOpts_descs[0], 1, 100, 20);
+	MenuInput_Enum(menuOpts_descs[1], FpsLimit_Names, FPS_LIMIT_COUNT);
+	MenuInput_Int(menuOpts_descs[2],  8, 4096, 512);
+	MenuInput_Enum(menuOpts_descs[4], LightingMode_Names, LIGHTING_MODE_COUNT)
+	MenuInput_Enum(menuOpts_descs[6], NameMode_Names,     NAME_MODE_COUNT);
+	MenuInput_Enum(menuOpts_descs[7], ShadowMode_Names,   SHADOW_MODE_COUNT);
+
+	MenuOptionsScreen_Show(GraphicsOptionsScreen_InitWidgets);
+}
+
+
+/*########################################################################################################################*
+*----------------------------------------------------ChatOptionsScreen-----------------------------------------------------*
+*#########################################################################################################################*/
+static void ChatOptionsScreen_SetScale(const cc_string* v, float* target, const char* optKey) {
+	*target = Menu_Float(v);
+	Options_Set(optKey, v);
+	Gui_LayoutAll();
+}
+
+static void ChatOptionsScreen_GetAutoScaleChat(cc_string* v) { Menu_GetBool(v, Gui.AutoScaleChat); }
+static void ChatOptionsScreen_SetAutoScaleChat(const cc_string* v) {
+	Gui.AutoScaleChat = Menu_SetBool(v, OPT_CHAT_AUTO_SCALE);
+	Gui_LayoutAll();
+}
+
+static void ChatOptionsScreen_GetChatScale(cc_string* v) { String_AppendFloat(v, Gui.RawChatScale, 1); }
+static void ChatOptionsScreen_SetChatScale(const cc_string* v) { ChatOptionsScreen_SetScale(v, &Gui.RawChatScale, OPT_CHAT_SCALE); }
+
+static void ChatOptionsScreen_GetChatlines(cc_string* v) { String_AppendInt(v, Gui.Chatlines); }
+static void ChatOptionsScreen_SetChatlines(const cc_string* v) {
+	Gui.Chatlines = Menu_Int(v);
+	ChatScreen_SetChatlines(Gui.Chatlines);
+	Options_Set(OPT_CHATLINES, v);
+}
+
+static void ChatOptionsScreen_GetLogging(cc_string* v) { Menu_GetBool(v, Chat_Logging); }
+static void ChatOptionsScreen_SetLogging(const cc_string* v) { 
+	Chat_Logging = Menu_SetBool(v, OPT_CHAT_LOGGING); 
+	if (!Chat_Logging) Chat_DisableLogging();
+}
+
+static void ChatOptionsScreen_GetClickable(cc_string* v) { Menu_GetBool(v, Gui.ClickableChat); }
+static void ChatOptionsScreen_SetClickable(const cc_string* v) { Gui.ClickableChat = Menu_SetBool(v, OPT_CLICKABLE_CHAT); }
+
+static void ChatOptionsScreen_InitWidgets(struct MenuOptionsScreen* s) {
+	static const struct MenuOptionDesc buttons[] = {
+		{ -1,  0, "Chat scale",         MenuOptionsScreen_Input,
+			ChatOptionsScreen_GetChatScale, ChatOptionsScreen_SetChatScale },
+		{ -1, 50, "Chat lines",         MenuOptionsScreen_Input,
+			ChatOptionsScreen_GetChatlines, ChatOptionsScreen_SetChatlines },
+
+		{  1,  0, "Log to disk",        MenuOptionsScreen_Bool,
+			ChatOptionsScreen_GetLogging,   ChatOptionsScreen_SetLogging },
+		{  1, 50, "Clickable chat",     MenuOptionsScreen_Bool,
+			ChatOptionsScreen_GetClickable, ChatOptionsScreen_SetClickable },
+
+		{ -1,-50, "Scale with window",         MenuOptionsScreen_Bool,
+			ChatOptionsScreen_GetAutoScaleChat, ChatOptionsScreen_SetAutoScaleChat }
+	};
+	MenuOptionsScreen_AddButtons(s, buttons, Array_Elems(buttons), Menu_SwitchOptions);
+}
+
+void ChatOptionsScreen_Show(void) {
+	MenuInput_Float(menuOpts_descs[0], 0.25f, 4.00f, 1);
+	MenuInput_Int(menuOpts_descs[1],       0,    30, Gui.DefaultLines);
+
+	MenuOptionsScreen_Show(ChatOptionsScreen_InitWidgets);
+}
+
+
+/*########################################################################################################################*
+*----------------------------------------------------GuiOptionsScreen-----------------------------------------------------*
+*#########################################################################################################################*/
+static void GuiOptionsScreen_GetShadows(cc_string* v) { Menu_GetBool(v, Drawer2D.BlackTextShadows); }
+static void GuiOptionsScreen_SetShadows(const cc_string* v) {
+	Drawer2D.BlackTextShadows = Menu_SetBool(v, OPT_BLACK_TEXT);
+	Event_RaiseVoid(&ChatEvents.FontChanged);
+}
+
+static void GuiOptionsScreen_GetShowFPS(cc_string* v) { Menu_GetBool(v, Gui.ShowFPS); }
+static void GuiOptionsScreen_SetShowFPS(const cc_string* v) { Gui.ShowFPS = Menu_SetBool(v, OPT_SHOW_FPS); }
+
+static void GuiOptionsScreen_GetHotbar(cc_string* v) { String_AppendFloat(v, Gui.RawHotbarScale, 1); }
+static void GuiOptionsScreen_SetHotbar(const cc_string* v) { ChatOptionsScreen_SetScale(v, &Gui.RawHotbarScale, OPT_HOTBAR_SCALE); }
+
+static void GuiOptionsScreen_GetInventory(cc_string* v) { String_AppendFloat(v, Gui.RawInventoryScale, 1); }
+static void GuiOptionsScreen_SetInventory(const cc_string* v) { ChatOptionsScreen_SetScale(v, &Gui.RawInventoryScale, OPT_INVENTORY_SCALE); }
+
+static void GuiOptionsScreen_GetCrosshair(cc_string* v) { String_AppendFloat(v, Gui.RawCrosshairScale, 1); }
+static void GuiOptionsScreen_SetCrosshair(const cc_string* v) { ChatOptionsScreen_SetScale(v, &Gui.RawCrosshairScale, OPT_CROSSHAIR_SCALE); }
+
+static void GuiOptionsScreen_GetTabAuto(cc_string* v) { Menu_GetBool(v, Gui.TabAutocomplete); }
+static void GuiOptionsScreen_SetTabAuto(const cc_string* v) { Gui.TabAutocomplete = Menu_SetBool(v, OPT_TAB_AUTOCOMPLETE); }
+
+static void GuiOptionsScreen_GetUseFont(cc_string* v) { Menu_GetBool(v, !Drawer2D.BitmappedText); }
+static void GuiOptionsScreen_SetUseFont(const cc_string* v) {
+	Drawer2D.BitmappedText = !Menu_SetBool(v, OPT_USE_CHAT_FONT);
+	Event_RaiseVoid(&ChatEvents.FontChanged);
+}
+
+static void GuiOptionsScreen_InitWidgets(struct MenuOptionsScreen* s) {
+	static const struct MenuOptionDesc buttons[] = {
+		
+		{ -1,  -100, "Show FPS",           MenuOptionsScreen_Bool,
+			GuiOptionsScreen_GetShowFPS,   GuiOptionsScreen_SetShowFPS },
+		{ -1,  -50, "Hotbar scale",       MenuOptionsScreen_Input,
+			GuiOptionsScreen_GetHotbar,    GuiOptionsScreen_SetHotbar },
+		{ -1,    0, "Inventory scale",    MenuOptionsScreen_Input,
+			GuiOptionsScreen_GetInventory, GuiOptionsScreen_SetInventory },
+		{ -1,   50, "Crosshair scale",    MenuOptionsScreen_Input,
+			GuiOptionsScreen_GetCrosshair, GuiOptionsScreen_SetCrosshair },
+
+		{ 1, -100, "Black text shadows", MenuOptionsScreen_Bool,
+			GuiOptionsScreen_GetShadows,   GuiOptionsScreen_SetShadows },
+		{ 1,  -50, "Tab auto-complete",  MenuOptionsScreen_Bool,
+			GuiOptionsScreen_GetTabAuto,   GuiOptionsScreen_SetTabAuto },
+		{ 1,    0, "Use system font",    MenuOptionsScreen_Bool,
+			GuiOptionsScreen_GetUseFont,   GuiOptionsScreen_SetUseFont },
+		{ 1,   50, "Select system font", Menu_SwitchFont,
+			NULL,                          NULL }
+	};
+	MenuOptionsScreen_AddButtons(s, buttons, Array_Elems(buttons), Menu_SwitchOptions);
+}
+
+void GuiOptionsScreen_Show(void) {
+	MenuInput_Float(menuOpts_descs[1], 0.25f, 4.00f, 1);
+	MenuInput_Float(menuOpts_descs[2], 0.25f, 4.00f, 1);
+	MenuInput_Float(menuOpts_descs[3], 0.25f, 4.00f, 1);
+
+	MenuOptionsScreen_Show(GuiOptionsScreen_InitWidgets);
+}
+
+
+/*########################################################################################################################*
+*---------------------------------------------------HacksSettingsScreen---------------------------------------------------*
+*#########################################################################################################################*/
+static void HacksSettingsScreen_GetHacks(cc_string* v) { Menu_GetBool(v, Entities.CurPlayer->Hacks.Enabled); }
+static void HacksSettingsScreen_SetHacks(const cc_string* v) {
+	Entities.CurPlayer->Hacks.Enabled = Menu_SetBool(v,OPT_HACKS_ENABLED);
+	HacksComp_Update(&Entities.CurPlayer->Hacks);
+}
+
+static void HacksSettingsScreen_GetSpeed(cc_string* v) { String_AppendFloat(v, Entities.CurPlayer->Hacks.SpeedMultiplier, 2); }
+static void HacksSettingsScreen_SetSpeed(const cc_string* v) {
+	Entities.CurPlayer->Hacks.SpeedMultiplier = Menu_Float(v);
+	Options_Set(OPT_SPEED_FACTOR, v);
+}
+
+static void HacksSettingsScreen_GetClipping(cc_string* v) { Menu_GetBool(v, Camera.Clipping); }
+static void HacksSettingsScreen_SetClipping(const cc_string* v) {
+	Camera.Clipping = Menu_SetBool(v, OPT_CAMERA_CLIPPING);
+}
+
+static void HacksSettingsScreen_GetJump(cc_string* v) { 
+	String_AppendFloat(v, LocalPlayer_JumpHeight(Entities.CurPlayer), 3); 
+}
+
+static void HacksSettingsScreen_SetJump(const cc_string* v) {
+	cc_string str; char strBuffer[STRING_SIZE];
+	struct PhysicsComp* physics;
+
+	physics = &Entities.CurPlayer->Physics;
+	physics->JumpVel     = PhysicsComp_CalcJumpVelocity(Menu_Float(v));
+	physics->UserJumpVel = physics->JumpVel;
+	
+	String_InitArray(str, strBuffer);
+	String_AppendFloat(&str, physics->JumpVel, 8);
+	Options_Set(OPT_JUMP_VELOCITY, &str);
+}
+
+static void HacksSettingsScreen_GetWOMHacks(cc_string* v) { Menu_GetBool(v, Entities.CurPlayer->Hacks.WOMStyleHacks); }
+static void HacksSettingsScreen_SetWOMHacks(const cc_string* v) {
+	Entities.CurPlayer->Hacks.WOMStyleHacks = Menu_SetBool(v, OPT_WOM_STYLE_HACKS);
+}
+
+static void HacksSettingsScreen_GetFullStep(cc_string* v) { Menu_GetBool(v, Entities.CurPlayer->Hacks.FullBlockStep); }
+static void HacksSettingsScreen_SetFullStep(const cc_string* v) {
+	Entities.CurPlayer->Hacks.FullBlockStep = Menu_SetBool(v, OPT_FULL_BLOCK_STEP);
+}
+
+static void HacksSettingsScreen_GetPushback(cc_string* v) { Menu_GetBool(v, Entities.CurPlayer->Hacks.PushbackPlacing); }
+static void HacksSettingsScreen_SetPushback(const cc_string* v) {
+	Entities.CurPlayer->Hacks.PushbackPlacing = Menu_SetBool(v, OPT_PUSHBACK_PLACING);
+}
+
+static void HacksSettingsScreen_GetLiquids(cc_string* v) { Menu_GetBool(v, Game_BreakableLiquids); }
+static void HacksSettingsScreen_SetLiquids(const cc_string* v) {
+	Game_BreakableLiquids = Menu_SetBool(v, OPT_MODIFIABLE_LIQUIDS);
+}
+
+static void HacksSettingsScreen_GetSlide(cc_string* v) { Menu_GetBool(v, Entities.CurPlayer->Hacks.NoclipSlide); }
+static void HacksSettingsScreen_SetSlide(const cc_string* v) {
+	Entities.CurPlayer->Hacks.NoclipSlide = Menu_SetBool(v, OPT_NOCLIP_SLIDE);
+}
+
+static void HacksSettingsScreen_GetFOV(cc_string* v) { String_AppendInt(v, Camera.Fov); }
+static void HacksSettingsScreen_SetFOV(const cc_string* v) {
+	int fov = Menu_Int(v);
+	if (Camera.ZoomFov > fov) Camera.ZoomFov = fov;
+	Camera.DefaultFov = fov;
+
+	Options_Set(OPT_FIELD_OF_VIEW, v);
+	Camera_SetFov(fov);
+}
+
+static void HacksSettingsScreen_CheckHacksAllowed(struct MenuOptionsScreen* s) {
+	struct Widget** widgets = s->widgets;
+	struct LocalPlayer* p   = Entities.CurPlayer;
+	cc_bool disabled        = !p->Hacks.Enabled;
+
+	Widget_SetDisabled(widgets[3], disabled || !p->Hacks.CanSpeed);
+	Widget_SetDisabled(widgets[4], disabled || !p->Hacks.CanSpeed);
+	Widget_SetDisabled(widgets[5], disabled || !p->Hacks.CanSpeed);
+	Widget_SetDisabled(widgets[7], disabled || !p->Hacks.CanPushbackBlocks);
+	MenuInputOverlay_CheckStillValid(s);
+}
+
+static void HacksSettingsScreen_InitWidgets(struct MenuOptionsScreen* s) {
+	static const struct MenuOptionDesc buttons[] = {
+		{ -1, -150, "Hacks enabled",    MenuOptionsScreen_Bool,
+			HacksSettingsScreen_GetHacks,    HacksSettingsScreen_SetHacks },
+		{ -1, -100, "Speed multiplier", MenuOptionsScreen_Input,
+			HacksSettingsScreen_GetSpeed,    HacksSettingsScreen_SetSpeed },
+		{ -1,  -50, "Camera clipping",  MenuOptionsScreen_Bool,
+			HacksSettingsScreen_GetClipping, HacksSettingsScreen_SetClipping },
+		{ -1,    0, "Jump height",      MenuOptionsScreen_Input,
+			HacksSettingsScreen_GetJump,     HacksSettingsScreen_SetJump },
+		{ -1,   50, "WoM style hacks",  MenuOptionsScreen_Bool,
+			HacksSettingsScreen_GetWOMHacks, HacksSettingsScreen_SetWOMHacks },
+	
+		{ 1, -150, "Full block stepping", MenuOptionsScreen_Bool,
+			HacksSettingsScreen_GetFullStep, HacksSettingsScreen_SetFullStep },
+		{ 1, -100, "Breakable liquids",   MenuOptionsScreen_Bool,
+			HacksSettingsScreen_GetLiquids,  HacksSettingsScreen_SetLiquids },
+		{ 1,  -50, "Pushback placing",    MenuOptionsScreen_Bool,
+			HacksSettingsScreen_GetPushback, HacksSettingsScreen_SetPushback },
+		{ 1,    0, "Noclip slide",        MenuOptionsScreen_Bool,
+			HacksSettingsScreen_GetSlide,    HacksSettingsScreen_SetSlide },
+		{ 1,   50, "Field of view",       MenuOptionsScreen_Input,
+			HacksSettingsScreen_GetFOV,      HacksSettingsScreen_SetFOV },
+	};
+	s->OnHacksChanged = HacksSettingsScreen_CheckHacksAllowed;
+
+	MenuOptionsScreen_AddButtons(s, buttons, Array_Elems(buttons), Menu_SwitchOptions);
+	HacksSettingsScreen_CheckHacksAllowed(s);
+
+	s->descriptions[2] = "&eIf &fON&e, then the third person cameras will limit\n&etheir zoom distance if they hit a solid block.";
+	s->descriptions[3] = "&eSets how many blocks high you can jump up.\n&eNote: You jump much higher when holding down the Speed key binding.";
+	s->descriptions[4] = \
+		"&eIf &fON&e, gives you a triple jump which increases speed massively,\n" \
+		"&ealong with older noclip style. This is based on the \"World of Minecraft\"\n" \
+		"&eclassic client mod, which popularized hacks conventions and controls\n" \
+		"&ebefore ClassiCube was created.";
+	s->descriptions[7] = \
+		"&eIf &fON&e, placing blocks that intersect your own position cause\n" \
+		"&ethe block to be placed, and you to be moved out of the way.\n" \
+		"&fThis is mainly useful for quick pillaring/towering.";
+	s->descriptions[8] = "&eIf &fOFF&e, you will immediately stop when in noclip\n&emode and no movement keys are held down.";
+}
+
+void HacksSettingsScreen_Show(void) {
+	MenuInput_Float(menuOpts_descs[1], 0.1f,   50, 10);
+	MenuInput_Float(menuOpts_descs[3], 0.1f, 2048, 1.233f);
+	MenuInput_Int(menuOpts_descs[9],      1,  179, 70);
+
+	MenuOptionsScreen_Show(HacksSettingsScreen_InitWidgets);
+}
+
+
+/*########################################################################################################################*
+*----------------------------------------------------MiscOptionsScreen----------------------------------------------------*
+*#########################################################################################################################*/
+static void MiscOptionsScreen_GetReach(cc_string* v) { String_AppendFloat(v, Entities.CurPlayer->ReachDistance, 2); }
+static void MiscOptionsScreen_SetReach(const cc_string* v) { Entities.CurPlayer->ReachDistance = Menu_Float(v); }
+
+static void MiscOptionsScreen_GetMusic(cc_string* v) { String_AppendInt(v, Audio_MusicVolume); }
+static void MiscOptionsScreen_SetMusic(const cc_string* v) {
+	Options_Set(OPT_MUSIC_VOLUME, v);
+	Audio_SetMusic(Menu_Int(v));
+}
+
+static void MiscOptionsScreen_GetSounds(cc_string* v) { String_AppendInt(v, Audio_SoundsVolume); }
+static void MiscOptionsScreen_SetSounds(const cc_string* v) {
+	Options_Set(OPT_SOUND_VOLUME, v);
+	Audio_SetSounds(Menu_Int(v));
+}
+
+static void MiscOptionsScreen_GetViewBob(cc_string* v) { Menu_GetBool(v, Game_ViewBobbing); }
+static void MiscOptionsScreen_SetViewBob(const cc_string* v) { Game_ViewBobbing = Menu_SetBool(v, OPT_VIEW_BOBBING); }
+
+static void MiscOptionsScreen_GetPhysics(cc_string* v) { Menu_GetBool(v, Physics.Enabled); }
+static void MiscOptionsScreen_SetPhysics(const cc_string* v) {
+	Physics_SetEnabled(Menu_SetBool(v, OPT_BLOCK_PHYSICS));
+}
+
+static void MiscOptionsScreen_GetInvert(cc_string* v) { Menu_GetBool(v, Camera.Invert); }
+static void MiscOptionsScreen_SetInvert(const cc_string* v) { Camera.Invert = Menu_SetBool(v, OPT_INVERT_MOUSE); }
+
+static void MiscOptionsScreen_GetSensitivity(cc_string* v) { String_AppendInt(v, Camera.Sensitivity); }
+static void MiscOptionsScreen_SetSensitivity(const cc_string* v) {
+	Camera.Sensitivity = Menu_Int(v);
+	Options_Set(OPT_SENSITIVITY, v);
+}
+
+static void MiscSettingsScreen_InitWidgets(struct MenuOptionsScreen* s) {
+	static const struct MenuOptionDesc buttons[] = {
+		{ -1, -100, "Reach distance", MenuOptionsScreen_Input,
+			MiscOptionsScreen_GetReach,       MiscOptionsScreen_SetReach },
+		{ -1,  -50, "Music volume",   MenuOptionsScreen_Input,
+			MiscOptionsScreen_GetMusic,       MiscOptionsScreen_SetMusic },
+		{ -1,    0, "Sounds volume",  MenuOptionsScreen_Input,
+			MiscOptionsScreen_GetSounds,      MiscOptionsScreen_SetSounds },
+		{ -1,   50, "View bobbing",   MenuOptionsScreen_Bool,
+			MiscOptionsScreen_GetViewBob,     MiscOptionsScreen_SetViewBob },
+	
+		{ 1, -100, "Block physics",       MenuOptionsScreen_Bool,
+			MiscOptionsScreen_GetPhysics,     MiscOptionsScreen_SetPhysics },
+		{ 1,    0, "Invert mouse",        MenuOptionsScreen_Bool,
+			MiscOptionsScreen_GetInvert,      MiscOptionsScreen_SetInvert },
+		{ 1,   50, "Mouse sensitivity",   MenuOptionsScreen_Input,
+			MiscOptionsScreen_GetSensitivity, MiscOptionsScreen_SetSensitivity }
+	};
+	MenuOptionsScreen_AddButtons(s, buttons, Array_Elems(buttons), Menu_SwitchOptions);
+
+	/* Disable certain options */
+	if (!Server.IsSinglePlayer) Menu_Remove(s, 0);
+	if (!Server.IsSinglePlayer) Menu_Remove(s, 4);
+}
+
+void MiscOptionsScreen_Show(void) {
+	MenuInput_Float(menuOpts_descs[0], 1, 1024, 5);
+	MenuInput_Int(menuOpts_descs[1],   0, 100,  DEFAULT_MUSIC_VOLUME);
+	MenuInput_Int(menuOpts_descs[2],   0, 100,  DEFAULT_SOUNDS_VOLUME);
+#ifdef CC_BUILD_WIN
+	MenuInput_Int(menuOpts_descs[6],   1, 200, 40);
+#else
+	MenuInput_Int(menuOpts_descs[6],   1, 200, 30);
+#endif
+
+	MenuOptionsScreen_Show(MiscSettingsScreen_InitWidgets);
+}
+
+
+/*########################################################################################################################*
+*--------------------------------------------------NostalgiaMenuScreen-----------------------------------------------------*
+*#########################################################################################################################*/
+static struct NostalgiaMenuScreen {
+	Screen_Body
+	struct ButtonWidget btnA, btnF, done;
+	struct TextWidget title;
+} NostalgiaMenuScreen;
+
+static struct Widget* nostalgiaMenu_widgets[4];
+
+static void NostalgiaMenuScreen_Appearance(void* a, void* b)    { NostalgiaAppearanceScreen_Show(); }
+static void NostalgiaMenuScreen_Functionality(void* a, void* b) { NostalgiaFunctionalityScreen_Show(); }
+
+static void NostalgiaMenuScreen_SwitchBack(void* a, void* b) {
+	if (Gui.ClassicMenu) { Menu_SwitchPause(a, b); } else { Menu_SwitchOptions(a, b); }
+}
+
+static void NostalgiaMenuScreen_ContextRecreated(void* screen) {
+	struct NostalgiaMenuScreen* s = (struct NostalgiaMenuScreen*)screen;
+	struct FontDesc titleFont;
+	Screen_UpdateVb(screen);
+	Gui_MakeTitleFont(&titleFont);
+
+	TextWidget_SetConst(&s->title, "Nostalgia options", &titleFont);
+	ButtonWidget_SetConst(&s->btnA, "Appearance",       &titleFont);
+	ButtonWidget_SetConst(&s->btnF, "Functionality",    &titleFont);
+	ButtonWidget_SetConst(&s->done,    "Done",          &titleFont);
+
+	Font_Free(&titleFont);
+}
+
+static void NostalgiaMenuScreen_Layout(void* screen) {
+	struct NostalgiaMenuScreen* s = (struct NostalgiaMenuScreen*)screen;
+	Widget_SetLocation(&s->title, ANCHOR_CENTRE, ANCHOR_CENTRE, 0, -100);
+	Widget_SetLocation(&s->btnA,  ANCHOR_CENTRE, ANCHOR_CENTRE, 0,  -25);
+	Widget_SetLocation(&s->btnF,  ANCHOR_CENTRE, ANCHOR_CENTRE, 0,   25);
+	Menu_LayoutBack(&s->done);
+}
+
+static void NostalgiaMenuScreen_Init(void* screen) {
+	struct NostalgiaMenuScreen* s = (struct NostalgiaMenuScreen*)screen;
+
+	s->widgets     = nostalgiaMenu_widgets;
+	s->numWidgets  = 0;
+	s->maxWidgets  = Array_Elems(nostalgiaMenu_widgets);
+
+	ButtonWidget_Add(s, &s->btnA, 400, NostalgiaMenuScreen_Appearance);
+	ButtonWidget_Add(s, &s->btnF, 400, NostalgiaMenuScreen_Functionality);
+	ButtonWidget_Add(s, &s->done, 400, NostalgiaMenuScreen_SwitchBack);
+	TextWidget_Add(s,   &s->title);
+
+	s->maxVertices = Screen_CalcDefaultMaxVertices(s);
+}
+
+static const struct ScreenVTABLE NostalgiaMenuScreen_VTABLE = {
+	NostalgiaMenuScreen_Init,  Screen_NullUpdate, Screen_NullFunc,
+	MenuScreen_Render2,        Screen_BuildMesh,
+	Menu_InputDown,            Screen_InputUp,    Screen_TKeyPress, Screen_TText,
+	Menu_PointerDown,          Screen_PointerUp,  Menu_PointerMove, Screen_TMouseScroll,
+	NostalgiaMenuScreen_Layout, Screen_ContextLost, NostalgiaMenuScreen_ContextRecreated
+};
+void NostalgiaMenuScreen_Show(void) {
+	struct NostalgiaMenuScreen* s = &NostalgiaMenuScreen;
+	s->grabsInput = true;
+	s->closable   = true;
+	s->VTABLE     = &NostalgiaMenuScreen_VTABLE;
+	Gui_Add((struct Screen*)s, GUI_PRIORITY_MENU);
+}
+
+
+/*########################################################################################################################*
+*------------------------------------------------NostalgiaAppearanceScreen------------------------------------------------*
+*#########################################################################################################################*/
+static void NostalgiaScreen_GetHand(cc_string* v) { Menu_GetBool(v, Models.ClassicArms); }
+static void NostalgiaScreen_SetHand(const cc_string* v) { Models.ClassicArms = Menu_SetBool(v, OPT_CLASSIC_ARM_MODEL); }
+
+static void NostalgiaScreen_GetAnim(cc_string* v) { Menu_GetBool(v, !Game_SimpleArmsAnim); }
+static void NostalgiaScreen_SetAnim(const cc_string* v) {
+	Game_SimpleArmsAnim = String_CaselessEqualsConst(v, "OFF");
+	Options_SetBool(OPT_SIMPLE_ARMS_ANIM, Game_SimpleArmsAnim);
+}
+
+static void NostalgiaScreen_GetClassicChat(cc_string* v) { Menu_GetBool(v, Gui.ClassicChat); }
+static void NostalgiaScreen_SetClassicChat(const cc_string* v) { Gui.ClassicChat = Menu_SetBool(v, OPT_CLASSIC_CHAT); }
+
+static void NostalgiaScreen_GetClassicInv(cc_string* v) { Menu_GetBool(v, Gui.ClassicInventory); }
+static void NostalgiaScreen_SetClassicInv(const cc_string* v) { Gui.ClassicInventory = Menu_SetBool(v, OPT_CLASSIC_INVENTORY); }
+
+static void NostalgiaScreen_GetGui(cc_string* v) { Menu_GetBool(v, Gui.ClassicTexture); }
+static void NostalgiaScreen_SetGui(const cc_string* v) { Gui.ClassicTexture = Menu_SetBool(v, OPT_CLASSIC_GUI); }
+
+static void NostalgiaScreen_GetList(cc_string* v) { Menu_GetBool(v, Gui.ClassicTabList); }
+static void NostalgiaScreen_SetList(const cc_string* v) { Gui.ClassicTabList = Menu_SetBool(v, OPT_CLASSIC_TABLIST); }
+
+static void NostalgiaScreen_GetOpts(cc_string* v) { Menu_GetBool(v, Gui.ClassicMenu); }
+static void NostalgiaScreen_SetOpts(const cc_string* v) { Gui.ClassicMenu = Menu_SetBool(v, OPT_CLASSIC_OPTIONS); }
+
+static void NostalgiaAppearanceScreen_InitWidgets(struct MenuOptionsScreen* s) {
+	static const struct MenuOptionDesc buttons[] = {
+		{ -1, -100, "Classic hand model",   MenuOptionsScreen_Bool,
+			NostalgiaScreen_GetHand,   NostalgiaScreen_SetHand },
+		{ -1,  -50, "Classic walk anim",    MenuOptionsScreen_Bool,
+			NostalgiaScreen_GetAnim,   NostalgiaScreen_SetAnim },
+        { -1,    0, "Classic chat",    MenuOptionsScreen_Bool,
+            NostalgiaScreen_GetClassicChat, NostalgiaScreen_SetClassicChat },
+		{ -1,  50, "Classic inventory", MenuOptionsScreen_Bool,
+			NostalgiaScreen_GetClassicInv,  NostalgiaScreen_SetClassicInv },
+		{  1,  -50, "Classic GUI textures", MenuOptionsScreen_Bool,
+			NostalgiaScreen_GetGui,    NostalgiaScreen_SetGui },
+		{  1,    0, "Classic player list",  MenuOptionsScreen_Bool,
+			NostalgiaScreen_GetList,   NostalgiaScreen_SetList },
+		{  1,   50, "Classic options",      MenuOptionsScreen_Bool,
+			NostalgiaScreen_GetOpts,   NostalgiaScreen_SetOpts },
+	};
+
+	MenuOptionsScreen_AddButtons(s, buttons, Array_Elems(buttons), Menu_SwitchNostalgia);
+}
+
+void NostalgiaAppearanceScreen_Show(void) {
+	MenuOptionsScreen_Show(NostalgiaAppearanceScreen_InitWidgets);
+}
+
+
+/*########################################################################################################################*
+*----------------------------------------------NostalgiaFunctionalityScreen-----------------------------------------------*
+*#########################################################################################################################*/
+static void NostalgiaScreen_UpdateVersionDisabled(void) {
+	struct ButtonWidget* gameVerBtn = &MenuOptionsScreen_Instance.buttons[3];
+	Widget_SetDisabled(gameVerBtn, Game_Version.HasCPE);
+}
+
+static void NostalgiaScreen_GetTexs(cc_string* v) { Menu_GetBool(v, Game_AllowServerTextures); }
+static void NostalgiaScreen_SetTexs(const cc_string* v) { Game_AllowServerTextures = Menu_SetBool(v, OPT_SERVER_TEXTURES); }
+
+static void NostalgiaScreen_GetCustom(cc_string* v) { Menu_GetBool(v, Game_AllowCustomBlocks); }
+static void NostalgiaScreen_SetCustom(const cc_string* v) { Game_AllowCustomBlocks = Menu_SetBool(v, OPT_CUSTOM_BLOCKS); }
+
+static void NostalgiaScreen_GetCPE(cc_string* v) { Menu_GetBool(v, Game_Version.HasCPE); }
+static void NostalgiaScreen_SetCPE(const cc_string* v) {
+	Menu_SetBool(v, OPT_CPE); 
+	GameVersion_Load();
+	NostalgiaScreen_UpdateVersionDisabled();
+}
+
+static void NostalgiaScreen_Version(void* screen, void* widget) {
+	struct MenuOptionsScreen* s = (struct MenuOptionsScreen*)screen;
+	int ver = Game_Version.Version - 1;
+	if (ver < VERSION_0017) ver = VERSION_0030;
+
+	Options_SetInt(OPT_GAME_VERSION, ver);
+	GameVersion_Load();
+	MenuOptionsScreen_Update(s, widget);
+}
+
+static void NostalgiaScreen_GetVersion(cc_string* v) { String_AppendConst(v, Game_Version.Name); }
+static void NostalgiaScreen_SetVersion(const cc_string* v) { }
+
+static struct TextWidget nostalgia_desc;
+static void NostalgiaScreen_RecreateExtra(struct MenuOptionsScreen* s) {
+	TextWidget_SetConst(&nostalgia_desc, "&eRequires restarting game to take full effect", &s->textFont);
+}
+
+static void NostalgiaFunctionalityScreen_InitWidgets(struct MenuOptionsScreen* s) {
+	static const struct MenuOptionDesc buttons[] = {
+		{ -1, -50, "Use server textures",  MenuOptionsScreen_Bool,
+			NostalgiaScreen_GetTexs,    NostalgiaScreen_SetTexs },
+		{ -1,   0, "Allow custom blocks",  MenuOptionsScreen_Bool,
+			NostalgiaScreen_GetCustom,  NostalgiaScreen_SetCustom },
+
+		{  1, -50, "Non-classic features", MenuOptionsScreen_Bool,
+			NostalgiaScreen_GetCPE,     NostalgiaScreen_SetCPE },
+		{  1,   0, "Game version",         NostalgiaScreen_Version,
+			NostalgiaScreen_GetVersion, NostalgiaScreen_SetVersion }
+	};
+	s->DoRecreateExtra = NostalgiaScreen_RecreateExtra;
+
+	MenuOptionsScreen_AddButtons(s, buttons, Array_Elems(buttons), Menu_SwitchNostalgia);
+	TextWidget_Add(s, &nostalgia_desc);
+	Widget_SetLocation(&nostalgia_desc, ANCHOR_CENTRE, ANCHOR_CENTRE, 0, 100);
+
+	NostalgiaScreen_UpdateVersionDisabled();
+	s->descriptions[3] = \
+		"&eNote that support for versions earlier than 0.30 is incomplete.\n" \
+		"\n" \
+		"&cNote that some servers only support 0.30 game version";
+}
+
+void NostalgiaFunctionalityScreen_Show(void) {
+	MenuOptionsScreen_Show(NostalgiaFunctionalityScreen_InitWidgets);
+}
+
+
+/*########################################################################################################################*
+*---------------------------------------------------------Overlay---------------------------------------------------------*
+*#########################################################################################################################*/
+static void Overlay_AddLabels(void* screen, struct TextWidget* labels) {
+	int i;
+	TextWidget_Add(screen, &labels[0]);
+	for (i = 1; i < 4; i++) 
+	{
+		TextWidget_Add(screen, &labels[i]);
+		labels[i].color = PackedCol_Make(224, 224, 224, 255);
+	}
+}
+
+static void Overlay_LayoutLabels(struct TextWidget* labels) {
+	int i;
+	Widget_SetLocation(&labels[0],     ANCHOR_CENTRE, ANCHOR_CENTRE, 0, -120);
+	for (i = 1; i < 4; i++) {
+		Widget_SetLocation(&labels[i], ANCHOR_CENTRE, ANCHOR_CENTRE, 0, -70 + 20 * i);
+	}
+}
+
+static void Overlay_LayoutMainButtons(struct ButtonWidget* btns) {
+	Widget_SetLocation(&btns[0], ANCHOR_CENTRE, ANCHOR_CENTRE, -110, 30);
+	Widget_SetLocation(&btns[1], ANCHOR_CENTRE, ANCHOR_CENTRE,  110, 30);
+}
+
+
+/*########################################################################################################################*
+*------------------------------------------------------TexIdsOverlay------------------------------------------------------*
+*#########################################################################################################################*/
+static struct TexIdsOverlay {
+	Screen_Body
+	int xOffset, yOffset, tileSize, textVertices;
+	struct TextAtlas idAtlas;
+	struct TextWidget title;
+} TexIdsOverlay;
+static struct Widget* texids_widgets[1];
+
+#define TEXIDS_MAX_ROWS_PER_PAGE 16
+#define TEXIDS_MAX_PER_PAGE      (TEXIDS_MAX_ROWS_PER_PAGE * ATLAS2D_TILES_PER_ROW)
+#define TEXIDS_TEXT_VERTICES (10 * 4 + 90 * 8 + 412 * 12) /* '0'-'9' + '10'-'99' + '100'-'511' */
+#define TEXIDS_MAX_VERTICES (TEXTWIDGET_MAX + 4 * ATLAS1D_MAX_ATLASES + TEXIDS_TEXT_VERTICES)
+
+static void TexIdsOverlay_Layout(void* screen) {
+	struct TexIdsOverlay* s = (struct TexIdsOverlay*)screen;
+	int size;
+
+	size = Window_UI.Height / ATLAS2D_TILES_PER_ROW;
+	size = (size / 8) * 8;
+	Math_Clamp(size, 8, 40);
+
+	s->xOffset  = Gui_CalcPos(ANCHOR_CENTRE, 0, size * Atlas2D.RowsCount,     Window_UI.Width);
+	s->yOffset  = Gui_CalcPos(ANCHOR_CENTRE, 0, size * ATLAS2D_TILES_PER_ROW, Window_UI.Height);
+	s->tileSize = size;
+
+	/* Can't use vertical centreing here */
+	Widget_SetLocation(&s->title, ANCHOR_CENTRE, ANCHOR_MIN, 0, 0);
+	s->title.yOffset = s->yOffset - Display_ScaleY(30);
+	Widget_Layout(&s->title);
+}
+
+static void TexIdsOverlay_ContextLost(void* screen) {
+	struct TexIdsOverlay* s = (struct TexIdsOverlay*)screen;
+	Screen_ContextLost(s);
+	TextAtlas_Free(&s->idAtlas);
+}
+
+static void TexIdsOverlay_ContextRecreated(void* screen) {
+	static const cc_string chars  = String_FromConst("0123456789");
+	static const cc_string prefix = String_FromConst("f");
+	struct TexIdsOverlay* s = (struct TexIdsOverlay*)screen;
+	struct FontDesc textFont, titleFont;
+
+	Screen_UpdateVb(screen);
+	Font_Make(&textFont, 8, FONT_FLAGS_PADDING);
+	Font_SetPadding(&textFont, 1);
+	TextAtlas_Make(&s->idAtlas, &chars, &textFont, &prefix);
+	Font_Free(&textFont);
+	
+	Gui_MakeTitleFont(&titleFont);
+	TextWidget_SetConst(&s->title, "Texture ID reference sheet", &titleFont);
+	Font_Free(&titleFont);
+}
+
+static void TexIdsOverlay_BuildTerrain(struct TexIdsOverlay* s, struct VertexTextured** ptr) {
+	struct Texture tex;
+	int baseLoc, xOffset;
+	int i, row, size;
+
+	size    = s->tileSize;
+	baseLoc = 0;
+	xOffset = s->xOffset;
+
+	tex.uv.u1 = 0.0f; tex.uv.u2 = UV2_Scale;
+	tex.width = size; tex.height = size;
+
+	for (row = 0; row < Atlas2D.RowsCount; row += TEXIDS_MAX_ROWS_PER_PAGE) {
+		for (i = 0; i < TEXIDS_MAX_PER_PAGE; i++) {
+
+			tex.x = xOffset    + Atlas2D_TileX(i) * size;
+			tex.y = s->yOffset + Atlas2D_TileY(i) * size;
+
+			tex.uv.v1 = Atlas1D_RowId(i + baseLoc) * Atlas1D.InvTileSize;
+			tex.uv.v2 = tex.uv.v1      + UV2_Scale * Atlas1D.InvTileSize;
+			
+			Gfx_Make2DQuad(&tex, PACKEDCOL_WHITE, ptr);
+		}
+
+		baseLoc += TEXIDS_MAX_PER_PAGE;
+		xOffset += size * ATLAS2D_TILES_PER_ROW;
+	}
+}
+
+static void TexIdsOverlay_BuildText(struct TexIdsOverlay* s, struct VertexTextured** ptr) {
+	struct TextAtlas* idAtlas;
+	struct VertexTextured* beg;
+	int xOffset, size, row;
+	int x, y, id = 0;
+
+	size    = s->tileSize;
+	xOffset = s->xOffset;
+	idAtlas = &s->idAtlas;
+	beg     = *ptr;
+	
+	for (row = 0; row < Atlas2D.RowsCount; row += TEXIDS_MAX_ROWS_PER_PAGE) {
+		idAtlas->tex.y = s->yOffset + (size - idAtlas->tex.height);
+
+		for (y = 0; y < ATLAS2D_TILES_PER_ROW; y++) {
+			for (x = 0; x < ATLAS2D_TILES_PER_ROW; x++) {
+				idAtlas->curX = xOffset + size * x + 3; /* offset text by 3 pixels */
+				TextAtlas_AddInt(idAtlas, id++, ptr);
+			}
+			idAtlas->tex.y += size;
+		}
+		xOffset += size * ATLAS2D_TILES_PER_ROW;
+	}	
+	s->textVertices = (int)(*ptr - beg);
+}
+
+static void TexIdsOverlay_BuildMesh(void* screen) {
+	struct TexIdsOverlay* s = (struct TexIdsOverlay*)screen;
+	struct VertexTextured* data;
+	struct VertexTextured** ptr;
+
+	data = Screen_LockVb(s);
+	ptr  = &data;
+
+	Widget_BuildMesh(&s->title, ptr);
+	TexIdsOverlay_BuildTerrain(s, ptr);
+	TexIdsOverlay_BuildText(s, ptr);
+	Gfx_UnlockDynamicVb(s->vb);
+}
+
+static int TexIdsOverlay_RenderTerrain(struct TexIdsOverlay* s, int offset) {
+	int i, count = Atlas1D.TilesPerAtlas * 4;
+	for (i = 0; i < Atlas1D.Count; i++) {
+		Gfx_BindTexture(Atlas1D.TexIds[i]);
+
+		Gfx_DrawVb_IndexedTris_Range(count, offset);
+		offset += count;
+	}
+	return offset;
+}
+
+static void TexIdsOverlay_OnAtlasChanged(void* screen) {
+	struct TexIdsOverlay* s = (struct TexIdsOverlay*)screen;
+	s->dirty = true;
+	/* Atlas may have 256 or 512 textures, which changes s->xOffset */
+	/* This can resize the position of the 'pages', so just re-layout */
+	TexIdsOverlay_Layout(screen);
+}
+
+static void TexIdsOverlay_Init(void* screen) {
+	struct TexIdsOverlay* s = (struct TexIdsOverlay*)screen;
+	s->widgets     = texids_widgets;
+	s->numWidgets  = 0;
+	s->maxWidgets  = Array_Elems(texids_widgets);
+	s->maxVertices = TEXIDS_MAX_VERTICES;
+
+	TextWidget_Add(s, &s->title);
+	Event_Register_(&TextureEvents.AtlasChanged, s, TexIdsOverlay_OnAtlasChanged);
+}
+
+static void TexIdsOverlay_Free(void* screen) {
+	struct TexIdsOverlay* s = (struct TexIdsOverlay*)screen;
+	Event_Unregister_(&TextureEvents.AtlasChanged, s, TexIdsOverlay_OnAtlasChanged);
+}
+
+static void TexIdsOverlay_Render(void* screen, float delta) {
+	struct TexIdsOverlay* s = (struct TexIdsOverlay*)screen;
+	int offset = 0;
+	Menu_RenderBounds();
+
+	Gfx_SetVertexFormat(VERTEX_FORMAT_TEXTURED);
+	Gfx_BindDynamicVb(s->vb);
+
+	offset = Widget_Render2(&s->title, offset);
+	offset = TexIdsOverlay_RenderTerrain(s, offset);
+
+	Gfx_BindTexture(s->idAtlas.tex.ID);
+	Gfx_DrawVb_IndexedTris_Range(s->textVertices, offset);
+}
+
+static int TexIdsOverlay_KeyDown(void* screen, int key) {
+	struct Screen* s = (struct Screen*)screen;
+	if (InputBind_Claims(BIND_IDOVERLAY, key)) { Gui_Remove(s); return true; }
+	return false;
+}
+
+static const struct ScreenVTABLE TexIdsOverlay_VTABLE = {
+	TexIdsOverlay_Init,    Screen_NullUpdate, TexIdsOverlay_Free,
+	TexIdsOverlay_Render,  TexIdsOverlay_BuildMesh,
+	TexIdsOverlay_KeyDown, Screen_InputUp,    Screen_FKeyPress, Screen_FText,
+	Menu_PointerDown,      Screen_PointerUp,  Menu_PointerMove, Screen_TMouseScroll,
+	TexIdsOverlay_Layout,  TexIdsOverlay_ContextLost, TexIdsOverlay_ContextRecreated
+};
+void TexIdsOverlay_Show(void) {
+	struct TexIdsOverlay* s = &TexIdsOverlay;
+	s->grabsInput = true;
+	s->closable   = true;
+	s->VTABLE     = &TexIdsOverlay_VTABLE;
+	Gui_Add((struct Screen*)s, GUI_PRIORITY_TEXIDS);
+}
+
+
+/*########################################################################################################################*
+*----------------------------------------------------UrlWarningOverlay----------------------------------------------------*
+*#########################################################################################################################*/
+static struct UrlWarningOverlay {
+	Screen_Body
+	cc_string url;
+	struct ButtonWidget btns[2];
+	struct TextWidget   lbls[4];
+	char _urlBuffer[STRING_SIZE * 4];
+} UrlWarningOverlay;
+
+static struct Widget* urlwarning_widgets[4 + 2];
+
+static void UrlWarningOverlay_OpenUrl(void* screen, void* b) {
+	struct UrlWarningOverlay* s = (struct UrlWarningOverlay*)screen;
+	cc_result res = Process_StartOpen(&s->url);
+	if (res) Logger_SimpleWarn2(res, "opening url in browser", &s->url);
+	Gui_Remove((struct Screen*)s);
+}
+
+static void UrlWarningOverlay_AppendUrl(void* screen, void* b) {
+	struct UrlWarningOverlay* s = (struct UrlWarningOverlay*)screen;
+	if (Gui.ClickableChat) ChatScreen_AppendInput(&s->url);
+	Gui_Remove((struct Screen*)s);
+}
+
+static void UrlWarningOverlay_ContextRecreated(void* screen) {
+	struct UrlWarningOverlay* s = (struct UrlWarningOverlay*)screen;
+	struct FontDesc titleFont, textFont;
+	Screen_UpdateVb(screen);
+
+	Gui_MakeTitleFont(&titleFont);
+	Gui_MakeBodyFont(&textFont);
+
+	TextWidget_SetConst(&s->lbls[0], "&eAre you sure you want to open this link?", &titleFont);
+	TextWidget_Set(&s->lbls[1],      &s->url,                                      &textFont);
+	TextWidget_SetConst(&s->lbls[2], "Be careful - links from strangers may be websites that", &textFont);
+	TextWidget_SetConst(&s->lbls[3], " have viruses, or things you may not want to open/see.", &textFont);
+
+	ButtonWidget_SetConst(&s->btns[0], "Yes", &titleFont);
+	ButtonWidget_SetConst(&s->btns[1], "No",  &titleFont);
+	Font_Free(&titleFont);
+	Font_Free(&textFont);
+}
+
+static void UrlWarningOverlay_Layout(void* screen) {
+	struct UrlWarningOverlay* s = (struct UrlWarningOverlay*)screen;
+	Overlay_LayoutLabels(s->lbls);
+	Overlay_LayoutMainButtons(s->btns);
+}
+
+static void UrlWarningOverlay_Init(void* screen) {
+	struct UrlWarningOverlay* s = (struct UrlWarningOverlay*)screen;
+	s->widgets     = urlwarning_widgets;
+	s->numWidgets  = 0;
+	s->maxWidgets  = Array_Elems(urlwarning_widgets);
+
+	Overlay_AddLabels(s, s->lbls);
+	ButtonWidget_Add(s, &s->btns[0], 160, UrlWarningOverlay_OpenUrl);
+	ButtonWidget_Add(s, &s->btns[1], 160, UrlWarningOverlay_AppendUrl);
+
+	s->maxVertices = Screen_CalcDefaultMaxVertices(s);
+}
+
+static const struct ScreenVTABLE UrlWarningOverlay_VTABLE = {
+	UrlWarningOverlay_Init,   Screen_NullUpdate,  Screen_NullFunc,  
+	MenuScreen_Render2,       Screen_BuildMesh,
+	Menu_InputDown,           Screen_InputUp,     Screen_TKeyPress, Screen_TText,
+	Menu_PointerDown,         Screen_PointerUp,   Menu_PointerMove, Screen_TMouseScroll,
+	UrlWarningOverlay_Layout, Screen_ContextLost, UrlWarningOverlay_ContextRecreated
+};
+void UrlWarningOverlay_Show(const cc_string* url) {
+	struct UrlWarningOverlay* s = &UrlWarningOverlay;
+	s->grabsInput = true;
+	s->closable   = true;
+	s->VTABLE     = &UrlWarningOverlay_VTABLE;
+
+	String_InitArray(s->url, s->_urlBuffer);
+	String_Copy(&s->url, url);
+	Gui_Add((struct Screen*)s, GUI_PRIORITY_URLWARNING);
+}
+
+
+/*########################################################################################################################*
+*-----------------------------------------------------TexPackOverlay------------------------------------------------------*
+*#########################################################################################################################*/
+static struct TexPackOverlay {
+	Screen_Body
+	cc_bool deny, alwaysDeny, gotContent;
+	cc_uint32 contentLength;
+	cc_string url;
+	int reqID;
+	struct FontDesc textFont;
+	struct ButtonWidget btns[4];
+	struct TextWidget   lbls[4];
+	char _urlBuffer[URL_MAX_SIZE];
+} TexPackOverlay;
+
+static struct Widget* texpack_widgets[4 + 4];
+
+static cc_bool TexPackOverlay_IsAlways(void* screen, void* w) { 
+	struct ButtonWidget* btn = (struct ButtonWidget*)w;
+	return btn->meta.val != 0;
+}
+
+static void TexPackOverlay_YesClick(void* screen, void* widget) {
+	struct TexPackOverlay* s = (struct TexPackOverlay*)screen;
+	TexturePack_Extract(&s->url);
+	if (TexPackOverlay_IsAlways(s, widget)) TextureCache_Accept(&s->url);
+	Gui_Remove((struct Screen*)s);
+}
+
+static void TexPackOverlay_NoClick(void* screen, void* widget) {
+	struct TexPackOverlay* s = (struct TexPackOverlay*)screen;
+	s->alwaysDeny = TexPackOverlay_IsAlways(s, widget);
+	s->deny       = true;
+	Gui_Refresh((struct Screen*)s);
+}
+
+static void TexPackOverlay_ConfirmNoClick(void* screen, void* b) {
+	struct TexPackOverlay* s = (struct TexPackOverlay*)screen;
+	if (s->alwaysDeny) TextureCache_Deny(&s->url);
+	Gui_Remove((struct Screen*)s);
+}
+
+static void TexPackOverlay_GoBackClick(void* screen, void* b) {
+	struct TexPackOverlay* s = (struct TexPackOverlay*)screen;
+	s->deny = false;
+	Gui_Refresh((struct Screen*)s);
+}
+
+static void TexPackOverlay_UpdateLine2(struct TexPackOverlay* s) {
+	static const cc_string https = String_FromConst("https://");
+	static const cc_string http  = String_FromConst("http://");
+	cc_string url = String_Empty;
+
+	if (!s->deny) {
+		url = s->url;
+		if (String_CaselessStarts(&url, &https)) {
+			url = String_UNSAFE_SubstringAt(&url, https.length);
+		}
+		if (String_CaselessStarts(&url, &http)) {
+			url = String_UNSAFE_SubstringAt(&url, http.length);
+		}
+	}
+	TextWidget_Set(&s->lbls[2], &url, &s->textFont);
+}
+
+static void TexPackOverlay_UpdateLine3(struct TexPackOverlay* s) {
+	cc_string contents; char contentsBuffer[STRING_SIZE];
+	float contentLengthMB;
+
+	if (s->deny) {
+		TextWidget_SetConst(&s->lbls[3], "Sure you don't want to download the texture pack?", &s->textFont);
+	} else if (s->contentLength) {
+		String_InitArray(contents, contentsBuffer);
+		contentLengthMB = s->contentLength / (1024.0f * 1024.0f);
+		String_Format1(&contents, "Download size: %f3 MB", &contentLengthMB);
+		TextWidget_Set(&s->lbls[3], &contents, &s->textFont);
+	} else if (s->gotContent) {
+		TextWidget_SetConst(&s->lbls[3], "Download size: Unknown", &s->textFont);
+	} else {
+		TextWidget_SetConst(&s->lbls[3], "Download size: Determining...", &s->textFont);
+	}
+}
+
+static void TexPackOverlay_Update(void* screen, float delta) {
+	struct TexPackOverlay* s = (struct TexPackOverlay*)screen;
+	struct HttpRequest item;
+	if (!Http_GetResult(s->reqID, &item)) return;
+
+	s->dirty         = true;
+	s->gotContent    = true;
+	s->contentLength = item.contentLength;
+
+	TexPackOverlay_UpdateLine3(s);
+	HttpRequest_Free(&item);
+}
+
+static void TexPackOverlay_ContextLost(void* screen) {
+	struct TexPackOverlay* s = (struct TexPackOverlay*)screen;
+	Font_Free(&s->textFont);
+	Screen_ContextLost(screen);
+}
+
+static void TexPackOverlay_ContextRecreated(void* screen) {
+	struct TexPackOverlay* s = (struct TexPackOverlay*)screen;
+	struct FontDesc titleFont;
+	Screen_UpdateVb(screen);
+
+	Gui_MakeTitleFont(&titleFont);
+	Gui_MakeBodyFont(&s->textFont);
+
+	TextWidget_SetConst(&s->lbls[0], s->deny  ? "&eYou might be missing out." 
+		: "Do you want to download the server's texture pack?", &titleFont);
+	TextWidget_SetConst(&s->lbls[1], !s->deny ? "Texture pack url:"
+		: "Texture packs can play a vital role in the look and feel of maps.", &s->textFont);
+	TexPackOverlay_UpdateLine2(s);
+	TexPackOverlay_UpdateLine3(s);
+
+	ButtonWidget_SetConst(&s->btns[0], s->deny ? "I'm sure" : "Yes", &titleFont);
+	ButtonWidget_SetConst(&s->btns[1], s->deny ? "Go back"  : "No",  &titleFont);
+	s->btns[0].MenuClick = s->deny ? TexPackOverlay_ConfirmNoClick : TexPackOverlay_YesClick;
+	s->btns[1].MenuClick = s->deny ? TexPackOverlay_GoBackClick    : TexPackOverlay_NoClick;
+
+	if (!s->deny) {
+		ButtonWidget_SetConst(&s->btns[2], "Always yes", &titleFont);
+		ButtonWidget_SetConst(&s->btns[3], "Always no",  &titleFont);
+		s->btns[2].MenuClick = TexPackOverlay_YesClick;
+		s->btns[3].MenuClick = TexPackOverlay_NoClick;
+		s->btns[2].meta.val  = 1;
+		s->btns[3].meta.val  = 1;
+	}
+
+	s->numWidgets = s->deny ? 6 : 8;
+	Font_Free(&titleFont);
+}
+
+static void TexPackOverlay_Layout(void* screen) {
+	struct TexPackOverlay* s = (struct TexPackOverlay*)screen;
+	Overlay_LayoutLabels(s->lbls);
+	Overlay_LayoutMainButtons(s->btns);
+	Widget_SetLocation(&s->btns[2], ANCHOR_CENTRE, ANCHOR_CENTRE, -110, 85);
+	Widget_SetLocation(&s->btns[3], ANCHOR_CENTRE, ANCHOR_CENTRE,  110, 85);
+}
+
+static void TexPackOverlay_Init(void* screen) {
+	struct TexPackOverlay* s = (struct TexPackOverlay*)screen;
+	s->widgets     = texpack_widgets;
+	s->numWidgets  = 0;
+	s->maxWidgets  = Array_Elems(texpack_widgets);
+
+	s->contentLength = 0;
+	s->gotContent    = false;
+	s->deny          = false;
+	Overlay_AddLabels(s, s->lbls);
+
+	ButtonWidget_Add(s, &s->btns[0], 160, NULL);
+	ButtonWidget_Add(s, &s->btns[1], 160, NULL);
+	ButtonWidget_Add(s, &s->btns[2], 160, NULL);
+	ButtonWidget_Add(s, &s->btns[3], 160, NULL);
+
+	s->maxVertices = Screen_CalcDefaultMaxVertices(s);
+}
+
+static const struct ScreenVTABLE TexPackOverlay_VTABLE = {
+	TexPackOverlay_Init,   TexPackOverlay_Update, Screen_NullFunc,
+	MenuScreen_Render2,    Screen_BuildMesh,
+	Menu_InputDown,        Screen_InputUp,        Screen_TKeyPress, Screen_TText,
+	Menu_PointerDown,      Screen_PointerUp,      Menu_PointerMove, Screen_TMouseScroll,
+	TexPackOverlay_Layout, TexPackOverlay_ContextLost, TexPackOverlay_ContextRecreated
+};
+void TexPackOverlay_Show(const cc_string* url) {
+	struct TexPackOverlay* s = &TexPackOverlay;
+	s->grabsInput = true;
+	/* Too easy to accidentally ESC this important dialog */
+	/* s->closable= true; */
+	s->VTABLE     = &TexPackOverlay_VTABLE;
+	
+	String_InitArray(s->url, s->_urlBuffer);
+	String_Copy(&s->url, url);
+
+	s->reqID = Http_AsyncGetHeaders(url, HTTP_FLAG_PRIORITY);
+	Gui_Add((struct Screen*)s, GUI_PRIORITY_TEXPACK);
+}