summary refs log tree commit diff
path: root/src/Screens.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/Screens.c
initial commit
Diffstat (limited to 'src/Screens.c')
-rw-r--r--src/Screens.c2199
1 files changed, 2199 insertions, 0 deletions
diff --git a/src/Screens.c b/src/Screens.c
new file mode 100644
index 0000000..61c307d
--- /dev/null
+++ b/src/Screens.c
@@ -0,0 +1,2199 @@
+#include "Screens.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 "TexturePack.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 "Menus.h"
+#include "World.h"
+#include "Input.h"
+#include "Utils.h"
+#include "Options.h"
+
+#define CHAT_MAX_STATUS Array_Elems(Chat_Status)
+#define CHAT_MAX_BOTTOMRIGHT Array_Elems(Chat_BottomRight)
+#define CHAT_MAX_CLIENTSTATUS Array_Elems(Chat_ClientStatus)
+
+int Screen_FInput(void* s, int key)             { return false; }
+int Screen_FKeyPress(void* s, char keyChar)     { return false; }
+int Screen_FText(void* s, const cc_string* str) { return false; }
+int Screen_FMouseScroll(void* s, float delta)   { return false; }
+int Screen_FPointer(void* s, int id, int x, int y) { return false; }
+
+int Screen_TInput(void* s, int key)             { return true; }
+int Screen_TKeyPress(void* s, char keyChar)     { return true; }
+int Screen_TText(void* s, const cc_string* str) { return true; }
+int Screen_TMouseScroll(void* s, float delta)   { return true; }
+int Screen_TPointer(void* s, int id, int x, int y) { return true; }
+
+void Screen_NullFunc(void* screen) { }
+void Screen_NullUpdate(void* screen, float delta) { }
+
+/* TODO: Remove these */
+struct HUDScreen;
+struct ChatScreen;
+static struct HUDScreen*  Gui_HUD;
+static struct ChatScreen* Gui_Chat;
+static cc_bool tablist_active;
+
+static cc_bool InventoryScreen_IsHotbarActive(void);
+CC_NOINLINE static cc_bool IsOnlyChatActive(void) {
+	struct Screen* s;
+	int i;
+
+	for (i = 0; i < Gui.ScreensCount; i++) {
+		s = Gui_Screens[i];
+		if (s->grabsInput && s != (struct Screen*)Gui_Chat) return false;
+	}
+	return true;
+}
+
+
+/*########################################################################################################################*
+*--------------------------------------------------------HUDScreen--------------------------------------------------------*
+*#########################################################################################################################*/
+static struct HUDScreen {
+	Screen_Body
+	struct FontDesc font;
+	struct TextWidget line1, line2;
+	struct TextAtlas posAtlas;
+	float accumulator;
+	int frames, posCount;
+	cc_bool hacksChanged;
+	float lastSpeed;
+	int lastFov;
+	int lastX, lastY, lastZ;
+	struct HotbarWidget hotbar;
+} HUDScreen_Instance;
+
+/* Each integer can be at most 10 digits + minus prefix */
+#define POSITION_VAL_CHARS 11
+/* [PREFIX] [(] [X] [,] [Y] [,] [Z] [)] */
+#define POSITION_HUD_CHARS (1 + 1 + POSITION_VAL_CHARS + 1 + POSITION_VAL_CHARS + 1 + POSITION_VAL_CHARS + 1)
+#define HUD_MAX_VERTICES (4 + TEXTWIDGET_MAX * 2 + HOTBAR_MAX_VERTICES + POSITION_HUD_CHARS * 4)
+
+static void HUDScreen_RemakeLine1(struct HUDScreen* s) {
+	cc_string status; char statusBuffer[STRING_SIZE * 2];
+	int indices, ping, fps;
+	float real_fps;
+
+	String_InitArray(status, statusBuffer);
+	/* Don't remake texture when FPS isn't being shown */
+	if (!Gui.ShowFPS && s->line1.tex.ID) return;
+	fps = s->accumulator == 0 ? 1 : (int)(s->frames / s->accumulator);
+
+	if (Gfx.ReducedPerfMode || (Gfx.ReducedPerfModeCooldown > 0)) {
+		String_AppendConst(&status, "(low perf mode), ");
+		Gfx.ReducedPerfModeCooldown--;
+	} else if (fps == 0) {
+		/* Running at less than 1 FPS.. */
+		real_fps = s->frames / s->accumulator;
+		String_Format1(&status, "%f(CH by &dWlodek&%f) 1 fps, ", &real_fps);
+	} else {
+		String_Format1(&status, "(CH by &dWlodekM&f) %i fps, ", &fps);
+	}
+
+	if (Game_ClassicMode) {
+		String_Format1(&status, "%i chunk updates", &Game.ChunkUpdates);
+	} else {
+		if (Game.ChunkUpdates) {
+			String_Format1(&status, "%i chunks/s, ", &Game.ChunkUpdates);
+		}
+
+		indices = ICOUNT(Game_Vertices);
+		String_Format1(&status, "%i vertices", &indices);
+
+		ping = Ping_AveragePingMS();
+		if (ping) String_Format1(&status, ", ping %i ms", &ping);
+	}
+	TextWidget_Set(&s->line1, &status, &s->font);
+	s->dirty = true;
+}
+
+static void HUDScreen_BuildPosition(struct HUDScreen* s, struct VertexTextured* data) {
+	struct VertexTextured* cur = data;
+	struct TextAtlas* atlas = &s->posAtlas;
+	struct Texture tex;
+	IVec3 pos;
+
+	/* Make "Position: " prefix */
+	tex = atlas->tex; 
+	tex.x     = 2 + DisplayInfo.ContentOffsetX;
+	tex.width = atlas->offset;
+	Gfx_Make2DQuad(&tex, PACKEDCOL_WHITE, &cur);
+
+	IVec3_Floor(&pos, &Entities.CurPlayer->Base.Position);
+	atlas->curX = tex.x + tex.width;
+
+	/* Make (X, Y, Z) suffix */
+	TextAtlas_Add(atlas,       13, &cur);
+	TextAtlas_AddInt(atlas, pos.x, &cur);
+	TextAtlas_Add(atlas,       11, &cur);
+	TextAtlas_AddInt(atlas, pos.y, &cur);
+	TextAtlas_Add(atlas,       11, &cur);
+	TextAtlas_AddInt(atlas, pos.z, &cur);
+	TextAtlas_Add(atlas,       14, &cur);
+
+	s->posCount = (int)(cur - data);
+}
+
+static cc_bool HUDScreen_HasHacksChanged(struct HUDScreen* s) {
+	struct HacksComp* hacks = &Entities.CurPlayer->Hacks;
+	float speed = HacksComp_CalcSpeedFactor(hacks, hacks->CanSpeed);
+	return speed != s->lastSpeed || Camera.Fov != s->lastFov || s->hacksChanged;
+}
+
+static void HUDScreen_RemakeLine2(struct HUDScreen* s) {
+	cc_string status; char statusBuffer[STRING_SIZE * 2];
+	struct HacksComp* hacks = &Entities.CurPlayer->Hacks;
+	float speed;
+	s->dirty = true;
+
+	if (Game_ClassicMode) {
+		TextWidget_SetConst(&s->line2, Game_Version.Name, &s->font);
+		return;
+	}
+
+	speed = HacksComp_CalcSpeedFactor(hacks, hacks->CanSpeed);
+	s->lastSpeed = speed; s->lastFov = Camera.Fov;
+	s->hacksChanged = false;
+
+	String_InitArray(status, statusBuffer);
+	if (Camera.Fov != Camera.DefaultFov) {
+		String_Format1(&status, "Zoom fov %i  ", &Camera.Fov);
+	}
+
+	if (hacks->Flying) String_AppendConst(&status, "Fly ON   ");
+	if (speed)         String_Format1(&status, "Speed %f1x   ", &speed);
+	if (hacks->Noclip) String_AppendConst(&status, "Noclip ON   ");
+
+	TextWidget_Set(&s->line2, &status, &s->font);
+}
+
+
+static void HUDScreen_ContextLost(void* screen) {
+	struct HUDScreen* s = (struct HUDScreen*)screen;
+	Font_Free(&s->font);
+	Screen_ContextLost(screen);
+
+	TextAtlas_Free(&s->posAtlas);
+	Elem_Free(&s->hotbar);
+	Elem_Free(&s->line1);
+	Elem_Free(&s->line2);
+}
+
+static void HUDScreen_ContextRecreated(void* screen) {	
+	static const cc_string chars  = String_FromConst("0123456789-, ()");
+	static const cc_string prefix = String_FromConst("Position: ");
+
+	struct HUDScreen* s = (struct HUDScreen*)screen;
+	Screen_UpdateVb(s);
+
+	Font_Make(&s->font, 16, FONT_FLAGS_PADDING);
+	Font_SetPadding(&s->font, 2);
+	HotbarWidget_SetFont(&s->hotbar, &s->font);
+
+	HUDScreen_RemakeLine1(s);
+	TextAtlas_Make(&s->posAtlas, &chars, &s->font, &prefix);
+	HUDScreen_RemakeLine2(s);
+}
+
+int HUDScreen_LayoutHotbar(void) {
+	struct HUDScreen* s = &HUDScreen_Instance;
+	s->hotbar.scale     = Gui_GetHotbarScale();
+	Widget_Layout(&s->hotbar);
+	return s->hotbar.height;
+}
+
+static void HUDScreen_Layout(void* screen) {
+	struct HUDScreen* s = (struct HUDScreen*)screen;
+	struct TextWidget* line1 = &s->line1;
+	struct TextWidget* line2 = &s->line2;
+	int posY;
+
+	Widget_SetLocation(line1, ANCHOR_MIN, ANCHOR_MIN, 
+						2 + DisplayInfo.ContentOffsetX, 2 + DisplayInfo.ContentOffsetY);
+	posY = line1->y + line1->height;
+	s->posAtlas.tex.y = posY;
+	Widget_SetLocation(line2, ANCHOR_MIN, ANCHOR_MIN, 
+						2 + DisplayInfo.ContentOffsetX, 0);
+
+	if (Game_ClassicMode) {
+		/* Swap around so 0.30 version is at top */
+		line2->yOffset = line1->yOffset;
+		line1->yOffset = posY;
+		Widget_Layout(line1);
+	} else {
+		/* We can't use y in TextWidget_Make because that DPI scales it */
+		line2->yOffset = posY + s->posAtlas.tex.height;
+	}
+
+	HUDScreen_LayoutHotbar();
+	Widget_Layout(line2);
+}
+
+static int HUDScreen_KeyDown(void* screen, int key) {
+	struct HUDScreen* s = (struct HUDScreen*)screen;
+	return Elem_HandlesKeyDown(&s->hotbar, key);
+}
+
+static void HUDScreen_InputUp(void* screen, int key) {
+	struct HUDScreen* s = (struct HUDScreen*)screen;
+	if (!InventoryScreen_IsHotbarActive()) return;
+	Elem_OnInputUp(&s->hotbar, key);
+}
+
+static int HUDscreen_PointerDown(void* screen, int id, int x, int y) {
+	struct HUDScreen* s = (struct HUDScreen*)screen;
+	if (Gui_TouchUI || Gui.InputGrab) {
+		return Elem_HandlesPointerDown(&s->hotbar, id, x, y);
+	}
+	return false;
+}
+
+static void HUDScreen_PointerUp(void *screen, int id, int x, int y) {
+	struct HUDScreen* s = (struct HUDScreen*)screen;
+	if (!Gui_TouchUI) return;
+	Elem_OnPointerUp(&s->hotbar, id, x, y);
+}
+
+static int HUDScreen_PointerMove(void *screen, int id, int x, int y) {
+	struct HUDScreen* s = (struct HUDScreen*)screen;
+	if (!Gui_TouchUI) return false;
+	return Elem_HandlesPointerMove(&s->hotbar, id, x, y);
+}
+
+static int HUDscreen_MouseScroll(void* screen, float delta) {
+	struct HUDScreen* s = (struct HUDScreen*)screen;
+	/* The default scrolling behaviour (e.g. camera, zoom) needs to be checked */
+	/*   BEFORE the hotbar is scrolled, but AFTER chat (maybe) handles scrolling. */
+	/* Therefore need to check the default behaviour here, hacky as that may be. */
+	if (Input_HandleMouseWheel(delta)) return false;
+
+	if (!Inventory.CanChangeSelected)  return false;
+	return Elem_HandlesMouseScroll(&s->hotbar, delta);
+}
+
+static void HUDScreen_HacksChanged(void* obj) {
+	((struct HUDScreen*)obj)->hacksChanged = true;
+}
+
+static void HUDScreen_NeedRedrawing(void* obj) {
+	((struct HUDScreen*)obj)->dirty = true;
+}
+
+static void HUDScreen_Init(void* screen) {
+	struct HUDScreen* s = (struct HUDScreen*)screen;
+	s->maxVertices      = HUD_MAX_VERTICES;
+
+	HotbarWidget_Create(&s->hotbar);
+	TextWidget_Init(&s->line1);
+	TextWidget_Init(&s->line2);
+	
+	s->line1.flags  |= WIDGET_FLAG_MAINSCREEN;
+	s->line2.flags  |= WIDGET_FLAG_MAINSCREEN;
+
+	Event_Register_(&UserEvents.HacksStateChanged, s, HUDScreen_HacksChanged);
+	Event_Register_(&TextureEvents.AtlasChanged,   s, HUDScreen_NeedRedrawing);
+	Event_Register_(&BlockEvents.BlockDefChanged,  s, HUDScreen_NeedRedrawing);
+}
+
+static void HUDScreen_Free(void* screen) {
+	Event_Unregister_(&UserEvents.HacksStateChanged, screen, HUDScreen_HacksChanged);
+	Event_Unregister_(&TextureEvents.AtlasChanged,   screen, HUDScreen_NeedRedrawing);
+	Event_Unregister_(&BlockEvents.BlockDefChanged,  screen, HUDScreen_NeedRedrawing);
+}
+
+static void HUDScreen_UpdateFPS(struct HUDScreen* s, float delta) {
+	s->frames++;
+	s->accumulator += delta;
+	if (s->accumulator < 1.0f) return;
+
+	HUDScreen_RemakeLine1(s);
+	s->accumulator    = 0.0f;
+	s->frames         = 0;
+	Game.ChunkUpdates = 0;
+}
+
+static void HUDScreen_Update(void* screen, float delta) {
+	struct HUDScreen* s = (struct HUDScreen*)screen;
+	IVec3 pos;
+
+	HUDScreen_UpdateFPS(s,          delta);
+	HotbarWidget_Update(&s->hotbar, delta);
+	if (Game_ClassicMode) return;
+
+	if (IsOnlyChatActive() && Gui.ShowFPS) {
+		if (HUDScreen_HasHacksChanged(s)) HUDScreen_RemakeLine2(s);
+	}
+
+	IVec3_Floor(&pos, &Entities.CurPlayer->Base.Position);
+	if (pos.x != s->lastX || pos.y != s->lastY || pos.z != s->lastZ)
+		s->dirty = true;
+}
+
+#define CH_EXTENT 16
+static void HUDScreen_BuildCrosshairsMesh(struct VertexTextured** ptr) {
+	/* Only top quarter of icons.png is used */
+	static struct Texture tex = { 0, Tex_Rect(0,0,0,0), Tex_UV(0.0f,0.0f, 15/256.0f,15/64.0f) };
+	int extent;
+
+	extent = (int)(CH_EXTENT * Gui_GetCrosshairScale());
+	tex.x  = (Window_Main.Width  / 2) - extent;
+	tex.y  = (Window_Main.Height / 2) - extent;
+
+	tex.width  = extent * 2;
+	tex.height = extent * 2;
+	Gfx_Make2DQuad(&tex, PACKEDCOL_WHITE, ptr);
+}
+
+static void HUDScreen_BuildMesh(void* screen) {
+	struct HUDScreen* s = (struct HUDScreen*)screen;
+	struct VertexTextured* data;
+	struct VertexTextured** ptr;
+
+	data = Screen_LockVb(s);
+	ptr  = &data;
+
+	HUDScreen_BuildCrosshairsMesh(ptr);
+	Widget_BuildMesh(&s->line1,  ptr);
+	Widget_BuildMesh(&s->line2,  ptr);
+	Widget_BuildMesh(&s->hotbar, ptr);
+
+	if (!Game_ClassicMode) 
+		HUDScreen_BuildPosition(s, data);
+	Gfx_UnlockDynamicVb(s->vb);
+}
+
+static void HUDScreen_Render(void* screen, float delta) {
+	struct HUDScreen* s = (struct HUDScreen*)screen;
+	if (Game_HideGui) return;
+
+	Gfx_3DS_SetRenderScreen(TOP_SCREEN);
+
+	Gfx_SetVertexFormat(VERTEX_FORMAT_TEXTURED);
+	Gfx_BindDynamicVb(s->vb);
+	if (Gui.ShowFPS) Widget_Render2(&s->line1, 4);
+
+	if (Game_ClassicMode) {
+		Widget_Render2(&s->line2, 8);
+	} else if (IsOnlyChatActive() && Gui.ShowFPS) {
+		Widget_Render2(&s->line2, 8);
+		Gfx_BindTexture(s->posAtlas.tex.ID);
+		Gfx_DrawVb_IndexedTris_Range(s->posCount, 12 + HOTBAR_MAX_VERTICES);
+		/* TODO swap these two lines back */
+	}
+
+	if (!Gui_GetBlocksWorld()) {
+		Gfx_BindDynamicVb(s->vb);
+		Widget_Render2(&s->hotbar, 12);
+
+		if (Gui.IconsTex && !tablist_active) {
+			Gfx_BindTexture(Gui.IconsTex);
+			Gfx_BindDynamicVb(s->vb); /* Have to rebind for mobile right now... */
+			Gfx_DrawVb_IndexedTris(4);
+		}
+	}
+
+	Gfx_3DS_SetRenderScreen(BOTTOM_SCREEN);
+}
+
+static const struct ScreenVTABLE HUDScreen_VTABLE = {
+	HUDScreen_Init,        HUDScreen_Update,    HUDScreen_Free,
+	HUDScreen_Render,      HUDScreen_BuildMesh,
+	HUDScreen_KeyDown,     HUDScreen_InputUp,   Screen_FKeyPress, Screen_FText,
+	HUDscreen_PointerDown, HUDScreen_PointerUp, HUDScreen_PointerMove,  HUDscreen_MouseScroll,
+	HUDScreen_Layout,      HUDScreen_ContextLost, HUDScreen_ContextRecreated
+};
+void HUDScreen_Show(void) {
+	struct HUDScreen* s = &HUDScreen_Instance;
+	s->VTABLE = &HUDScreen_VTABLE;
+	Gui_HUD   = s;
+	Gui_Add((struct Screen*)s, GUI_PRIORITY_HUD);
+}
+
+
+/*########################################################################################################################*
+*----------------------------------------------------TabListOverlay-----------------------------------------------------*
+*#########################################################################################################################*/
+#define GROUP_NAME_ID UInt16_MaxValue
+#define LIST_COLUMN_PADDING 5
+#define LIST_NAMES_PER_COLUMN 16
+#define TABLIST_MAX_ENTRIES (TABLIST_MAX_NAMES * 2)
+typedef int (*TabListEntryCompare)(int x, int y);
+
+static struct TabListOverlay {
+	Screen_Body
+	int x, y, width, height;
+	cc_bool classic, staysOpen;
+	int usedCount, elementOffset;
+	struct TextWidget title;
+	struct FontDesc font;
+	TabListEntryCompare compare;
+	cc_uint16 ids[TABLIST_MAX_ENTRIES];
+	struct Texture textures[TABLIST_MAX_ENTRIES];
+} TabListOverlay_Instance;
+#define TABLIST_MAX_VERTICES (TEXTWIDGET_MAX + 4 * TABLIST_MAX_ENTRIES)
+
+static void TabListOverlay_DrawText(struct Texture* tex, struct TabListOverlay* s, const cc_string* name) {
+	cc_string tmp; char tmpBuffer[STRING_SIZE];
+	struct DrawTextArgs args;
+
+	if (Game_PureClassic) {
+		String_InitArray(tmp, tmpBuffer);
+		String_AppendColorless(&tmp, name);
+	} else {
+		tmp = *name;
+	}
+
+	DrawTextArgs_Make(&args, &tmp, &s->font, !s->classic);
+	Drawer2D_MakeTextTexture(tex, &args);
+}
+
+static int TabListOverlay_GetColumnWidth(struct TabListOverlay* s, int column) {
+	int i   = column * LIST_NAMES_PER_COLUMN;
+	int end = min(s->usedCount, i + LIST_NAMES_PER_COLUMN);
+	int maxWidth = 0;
+
+	for (; i < end; i++) 
+	{
+		maxWidth = max(maxWidth, s->textures[i].width);
+	}
+	return maxWidth + LIST_COLUMN_PADDING + s->elementOffset;
+}
+
+static int TabListOverlay_GetColumnHeight(struct TabListOverlay* s, int column) {
+	int i   = column * LIST_NAMES_PER_COLUMN;
+	int end = min(s->usedCount, i + LIST_NAMES_PER_COLUMN);
+	int height = 0;
+
+	for (; i < end; i++) 
+	{
+		height += s->textures[i].height + 1;
+	}
+	return height;
+}
+
+static void TabListOverlay_SetColumnPos(struct TabListOverlay* s, int column, int x, int y) {
+	struct Texture tex;
+	int i   = column * LIST_NAMES_PER_COLUMN;
+	int end = min(s->usedCount, i + LIST_NAMES_PER_COLUMN);
+
+	for (; i < end; i++) 
+	{
+		tex = s->textures[i];
+		tex.x = x; tex.y = y - 10;
+
+		y += tex.height + 1;
+		/* offset player names a bit, compared to group name */
+		if (!s->classic && s->ids[i] != GROUP_NAME_ID) {
+			tex.x += s->elementOffset;
+		}
+		s->textures[i] = tex;
+	}
+}
+
+static void TabListOverlay_Layout(void* screen) {
+	struct TabListOverlay* s = (struct TabListOverlay*)screen;
+	int minWidth, minHeight, paddingX, paddingY;
+	int i, x, y, width = 0, height = 0;
+	int columns = Math_CeilDiv(s->usedCount, LIST_NAMES_PER_COLUMN);
+
+	for (i = 0; i < columns; i++) 
+	{
+		width += TabListOverlay_GetColumnWidth(s,  i);
+		y      = TabListOverlay_GetColumnHeight(s, i);
+		height = max(height, y);
+	}
+
+	minWidth = Display_ScaleX(480);
+	width    = max(width, minWidth);
+	paddingX = Display_ScaleX(10);
+	paddingY = Display_ScaleY(10);
+
+	width  += paddingX * 2;
+	height += paddingY * 2;
+
+	y    = Window_UI.Height / 4 - height / 2;
+	s->x = Gui_CalcPos(ANCHOR_CENTRE,          0, width , Window_UI.Width );
+	s->y = Gui_CalcPos(ANCHOR_CENTRE, -max(0, y), height, Window_UI.Height);
+
+	x = s->x + paddingX;
+	y = s->y + paddingY;
+
+	for (i = 0; i < columns; i++) 
+	{
+		TabListOverlay_SetColumnPos(s, i, x, y);
+		x += TabListOverlay_GetColumnWidth(s, i);
+	}
+
+	s->y -= (s->title.height + paddingY);
+	s->width  = width;
+	minHeight = Display_ScaleY(300);
+	s->height = max(minHeight, height + s->title.height);
+
+	s->title.horAnchor = ANCHOR_CENTRE;
+	s->title.yOffset   = s->y + paddingY / 2;
+	Widget_Layout(&s->title);
+}
+
+static void TabListOverlay_AddName(struct TabListOverlay* s, EntityID id, int index) {
+	cc_string name;
+	/* insert at end of list */
+	if (index == -1) { index = s->usedCount; s->usedCount++; }
+
+	name = TabList_UNSAFE_GetList(id);
+	s->ids[index] = id;
+	TabListOverlay_DrawText(&s->textures[index], s, &name);
+}
+
+static void TabListOverlay_DeleteAt(struct TabListOverlay* s, int i) {
+	Gfx_DeleteTexture(&s->textures[i].ID);
+
+	for (; i < s->usedCount - 1; i++)
+	{
+		s->ids[i]      = s->ids[i + 1];
+		s->textures[i] = s->textures[i + 1];
+	}
+
+	s->usedCount--;
+	s->ids[s->usedCount]         = 0;
+	s->textures[s->usedCount].ID = 0;
+}
+
+static void TabListOverlay_AddGroup(struct TabListOverlay* s, int id, int* index) {
+	cc_string group;
+	int i;
+	group = TabList_UNSAFE_GetGroup(id);
+
+	for (i = Array_Elems(s->ids) - 1; i > (*index); i--) 
+	{
+		s->ids[i]      = s->ids[i - 1];
+		s->textures[i] = s->textures[i - 1];
+	}
+	
+	s->ids[*index] = GROUP_NAME_ID;
+	s->textures[*index].ID = 0; /* TODO: TEMP HACK! */
+	TabListOverlay_DrawText(&s->textures[*index], s, &group);
+
+	(*index)++;
+	s->usedCount++;
+}
+
+static int TabListOverlay_GetGroupCount(struct TabListOverlay* s, int id, int i) {
+	cc_string group, curGroup;
+	int count;
+	group = TabList_UNSAFE_GetGroup(id);
+
+	for (count = 0; i < s->usedCount; i++, count++)
+	{
+		curGroup = TabList_UNSAFE_GetGroup(s->ids[i]);
+		if (!String_CaselessEquals(&group, &curGroup)) break;
+	}
+	return count;
+}
+
+static int TabListOverlay_PlayerCompare(int x, int y) {
+	cc_string xName; char xNameBuffer[STRING_SIZE];
+	cc_string yName; char yNameBuffer[STRING_SIZE];
+	cc_uint8 xRank, yRank;
+	cc_string xNameRaw, yNameRaw;
+
+	xRank = TabList.GroupRanks[x];
+	yRank = TabList.GroupRanks[y];
+	if (xRank != yRank) return (xRank < yRank ? -1 : 1);
+	
+	String_InitArray(xName, xNameBuffer);
+	xNameRaw = TabList_UNSAFE_GetList(x);
+	String_AppendColorless(&xName, &xNameRaw);
+
+	String_InitArray(yName, yNameBuffer);
+	yNameRaw = TabList_UNSAFE_GetList(y);
+	String_AppendColorless(&yName, &yNameRaw);
+
+	return String_Compare(&xName, &yName);
+}
+
+static int TabListOverlay_GroupCompare(int x, int y) {
+	cc_string xGroup, yGroup;
+	/* TODO: should we use colourless comparison? ClassicalSharp sorts groups with colours */
+	xGroup = TabList_UNSAFE_GetGroup(x);
+	yGroup = TabList_UNSAFE_GetGroup(y);
+	return String_Compare(&xGroup, &yGroup);
+}
+
+static void TabListOverlay_QuickSort(int left, int right) {
+	struct Texture* values = TabListOverlay_Instance.textures; struct Texture value;
+	cc_uint16* keys        = TabListOverlay_Instance.ids; cc_uint16 key;
+	TabListEntryCompare compareEntries = TabListOverlay_Instance.compare;
+
+	while (left < right) {
+		int i = left, j = right;
+		int pivot = keys[(i + j) / 2];
+
+		/* partition the list */
+		while (i <= j) {
+			while (compareEntries(pivot, keys[i]) > 0) i++;
+			while (compareEntries(pivot, keys[j]) < 0) j--;
+			QuickSort_Swap_KV_Maybe();
+		}
+		/* recurse into the smaller subset */
+		QuickSort_Recurse(TabListOverlay_QuickSort)
+	}
+}
+
+static void TabListOverlay_SortEntries(struct TabListOverlay* s) {
+	int i, id, count;
+	if (!s->usedCount) return;
+
+	if (s->classic) {
+		TabListOverlay_Instance.compare = TabListOverlay_PlayerCompare;
+		TabListOverlay_QuickSort(0, s->usedCount - 1);
+		return;
+	}
+
+	/* Sort the list by group */
+	/* Loop backwards, since DeleteAt() reduces NamesCount */
+	for (i = s->usedCount - 1; i >= 0; i--)
+	{
+		if (s->ids[i] != GROUP_NAME_ID) continue;
+		TabListOverlay_DeleteAt(s, i);
+	}
+	TabListOverlay_Instance.compare = TabListOverlay_GroupCompare;
+	TabListOverlay_QuickSort(0, s->usedCount - 1);
+
+	/* Sort the entries in each group */
+	TabListOverlay_Instance.compare = TabListOverlay_PlayerCompare;
+	for (i = 0; i < s->usedCount; )
+	{
+		id = s->ids[i];
+		TabListOverlay_AddGroup(s, id, &i);
+
+		count = TabListOverlay_GetGroupCount(s, id, i);
+		TabListOverlay_QuickSort(i, i + (count - 1));
+		i += count;
+	}
+}
+
+static void TabListOverlay_SortAndLayout(struct TabListOverlay* s) {
+	TabListOverlay_SortEntries(s);
+	TabListOverlay_Layout(s);
+	s->dirty = true;
+}
+
+static void TabListOverlay_Add(void* obj, int id) {
+	struct TabListOverlay* s = (struct TabListOverlay*)obj;
+	TabListOverlay_AddName(s, id, -1);
+	TabListOverlay_SortAndLayout(s);
+}
+
+static void TabListOverlay_Update(void* obj, int id) {
+	struct TabListOverlay* s = (struct TabListOverlay*)obj;
+	int i;
+	for (i = 0; i < s->usedCount; i++)
+	{
+		if (s->ids[i] != id) continue;
+		Gfx_DeleteTexture(&s->textures[i].ID);
+
+		TabListOverlay_AddName(s, id, i);
+		TabListOverlay_SortAndLayout(s);
+		return;
+	}
+}
+
+static void TabListOverlay_Remove(void* obj, int id) {
+	struct TabListOverlay* s = (struct TabListOverlay*)obj;
+	int i;
+	for (i = 0; i < s->usedCount; i++)
+	{
+		if (s->ids[i] != id) continue;
+
+		TabListOverlay_DeleteAt(s, i);
+		TabListOverlay_SortAndLayout(s);
+		return;
+	}
+}
+
+static int TabListOverlay_PointerDown(void* screen, int id, int x, int y) {
+	struct TabListOverlay* s = (struct TabListOverlay*)screen;
+	cc_string text; char textBuffer[STRING_SIZE * 4];
+	struct Texture tex;
+	cc_string player;
+	int i;
+
+	if (!((struct Screen*)Gui_Chat)->grabsInput) return false;
+	String_InitArray(text, textBuffer);
+
+	for (i = 0; i < s->usedCount; i++)
+	{
+		if (!s->textures[i].ID || s->ids[i] == GROUP_NAME_ID) continue;
+		tex = s->textures[i];
+		if (!Gui_Contains(tex.x, tex.y, tex.width, tex.height, x, y)) continue;
+
+		player = TabList_UNSAFE_GetPlayer(s->ids[i]);
+		String_Format1(&text, "%s ", &player);
+		ChatScreen_AppendInput(&text);
+		return TOUCH_TYPE_GUI;
+	}
+	return false;
+}
+
+static void TabListOverlay_KeyUp(void* screen, int key) {
+	struct TabListOverlay* s = (struct TabListOverlay*)screen;
+	if (!InputBind_Claims(BIND_TABLIST, key) || s->staysOpen) return;
+	Gui_Remove((struct Screen*)s);
+}
+
+static void TabListOverlay_ContextLost(void* screen) {
+	struct TabListOverlay* s = (struct TabListOverlay*)screen;
+	int i;
+	for (i = 0; i < s->usedCount; i++)
+	{
+		Gfx_DeleteTexture(&s->textures[i].ID);
+	}
+
+	Elem_Free(&s->title);
+	Font_Free(&s->font);
+	Screen_ContextLost(screen);
+}
+
+static void TabListOverlay_ContextRecreated(void* screen) {
+	struct TabListOverlay* s = (struct TabListOverlay*)screen;
+	int size, id;
+
+	size = Drawer2D.BitmappedText ? 16 : 11;
+	Font_Make(&s->font, size, FONT_FLAGS_PADDING);
+	s->usedCount = 0;
+
+	TextWidget_SetConst(&s->title, "Connected players:", &s->font);
+	Font_SetPadding(&s->font, 1);
+	Screen_UpdateVb(screen);
+
+	/* TODO: Just recreate instead of this? maybe */
+	for (id = 0; id < TABLIST_MAX_NAMES; id++) 
+	{
+		if (!TabList.NameOffsets[id]) continue;
+		TabListOverlay_AddName(s, (EntityID)id, -1);
+	}
+	TabListOverlay_SortAndLayout(s); /* TODO: Not do layout here too */
+}
+
+static void TabListOverlay_BuildMesh(void* screen) {
+	struct TabListOverlay* s = (struct TabListOverlay*)screen;
+	struct Screen*   grabbed = Gui_GetInputGrab();
+	struct VertexTextured* v;
+	struct Texture tex;
+	int i;
+	
+	v = (struct VertexTextured*)Gfx_LockDynamicVb(s->vb,
+										VERTEX_FORMAT_TEXTURED, TEXTWIDGET_MAX + s->usedCount * 4);
+	Widget_BuildMesh(&s->title, &v);
+
+	for (i = 0; i < s->usedCount; i++)
+	{
+		if (!s->textures[i].ID) continue;
+		tex = s->textures[i];
+
+		if (grabbed && s->ids[i] != GROUP_NAME_ID) {
+			if (Gui_ContainsPointers(tex.x, tex.y, tex.width, tex.height)) tex.x += 4;
+		}
+		Gfx_Make2DQuad(&tex, PACKEDCOL_WHITE, &v);
+	}
+	Gfx_UnlockDynamicVb(s->vb);
+}
+
+static void TabListOverlay_Render(void* screen, float delta) {
+	struct TabListOverlay* s = (struct TabListOverlay*)screen;
+	int i, offset = 0;
+	PackedCol topCol    = PackedCol_Make( 0,  0,  0, 180);
+	PackedCol bottomCol = PackedCol_Make(50, 50, 50, 205);
+
+	if (Game_HideGui || !IsOnlyChatActive()) return;
+
+	Gfx_3DS_SetRenderScreen(TOP_SCREEN);
+
+	Gfx_Draw2DGradient(s->x, s->y, s->width, s->height, topCol, bottomCol);
+
+	Gfx_SetVertexFormat(VERTEX_FORMAT_TEXTURED);
+	Gfx_BindDynamicVb(s->vb);
+	offset = Widget_Render2(&s->title, offset);
+
+	for (i = 0; i < s->usedCount; i++)
+	{
+		if (!s->textures[i].ID) continue;
+		Gfx_BindTexture(s->textures[i].ID);
+
+		Gfx_DrawVb_IndexedTris_Range(4, offset);
+		offset += 4;
+	}
+
+	Gfx_3DS_SetRenderScreen(BOTTOM_SCREEN);
+}
+
+static void TabListOverlay_Free(void* screen) {
+	struct TabListOverlay* s = (struct TabListOverlay*)screen;
+	tablist_active = false;
+	Event_Unregister_(&TabListEvents.Added,   s, TabListOverlay_Add);
+	Event_Unregister_(&TabListEvents.Changed, s, TabListOverlay_Update);
+	Event_Unregister_(&TabListEvents.Removed, s, TabListOverlay_Remove);
+}
+
+static void TabListOverlay_Init(void* screen) {
+	struct TabListOverlay* s = (struct TabListOverlay*)screen;
+	tablist_active   = true;
+	s->classic       = Gui.ClassicTabList || !Server.SupportsExtPlayerList;
+	s->elementOffset = s->classic ? 0 : 10;
+	s->maxVertices   = TABLIST_MAX_VERTICES;
+	TextWidget_Init(&s->title);
+
+	Event_Register_(&TabListEvents.Added,   s, TabListOverlay_Add);
+	Event_Register_(&TabListEvents.Changed, s, TabListOverlay_Update);
+	Event_Register_(&TabListEvents.Removed, s, TabListOverlay_Remove);
+}
+
+static const struct ScreenVTABLE TabListOverlay_VTABLE = {
+	TabListOverlay_Init,        Screen_NullUpdate,     TabListOverlay_Free,
+	TabListOverlay_Render,      TabListOverlay_BuildMesh,
+	Screen_FInput,              TabListOverlay_KeyUp,  Screen_FKeyPress, Screen_FText,
+	TabListOverlay_PointerDown, Screen_PointerUp,      Screen_FPointer,  Screen_FMouseScroll,
+	TabListOverlay_Layout, TabListOverlay_ContextLost, TabListOverlay_ContextRecreated
+};
+void TabListOverlay_Show(cc_bool staysOpen) {
+	struct TabListOverlay* s  = &TabListOverlay_Instance;
+	s->VTABLE    = &TabListOverlay_VTABLE;
+	s->staysOpen = staysOpen;
+	Gui_Add((struct Screen*)s, GUI_PRIORITY_TABLIST);
+}
+
+
+/*########################################################################################################################*
+*--------------------------------------------------------ChatScreen-------------------------------------------------------*
+*#########################################################################################################################*/
+static struct ChatScreen {
+	Screen_Body
+	float chatAcc;
+	cc_bool suppressNextPress;
+	int chatIndex, paddingX, paddingY;
+	int lastDownloadStatus;
+	struct FontDesc chatFont, announcementFont, bigAnnouncementFont, smallAnnouncementFont;
+	struct TextWidget announcement, bigAnnouncement, smallAnnouncement;
+	struct ChatInputWidget input;
+	struct TextGroupWidget status, bottomRight, chat, clientStatus;
+	struct SpecialInputWidget altText;
+#ifdef CC_BUILD_TOUCH
+	struct ButtonWidget send, cancel, more;
+#endif
+
+	struct Texture statusTextures[CHAT_MAX_STATUS];
+	struct Texture bottomRightTextures[CHAT_MAX_BOTTOMRIGHT];
+	struct Texture clientStatusTextures[CHAT_MAX_CLIENTSTATUS];
+	struct Texture chatTextures[GUI_MAX_CHATLINES];
+} ChatScreen_Instance;
+
+static void ChatScreen_UpdateChatYOffsets(struct ChatScreen* s) {
+	int pad, y;
+	/* Determining chat Y requires us to know hotbar's position */
+	HUDScreen_LayoutHotbar();
+		
+	y = min(s->input.base.y, Gui_HUD->hotbar.y);
+	y -= s->input.base.yOffset; /* add some padding */
+	s->altText.yOffset = Window_UI.Height - y;
+	Widget_Layout(&s->altText);
+
+	pad = s->altText.active ? 5 : 10;
+	s->clientStatus.yOffset = Window_UI.Height - s->altText.y + pad;
+	Widget_Layout(&s->clientStatus);
+	s->chat.yOffset = s->clientStatus.yOffset + s->clientStatus.height;
+	Widget_Layout(&s->chat);
+}
+
+static void ChatScreen_OnInputTextChanged(void* elem) {
+	ChatScreen_UpdateChatYOffsets(&ChatScreen_Instance);
+}
+
+static cc_string ChatScreen_GetChat(int i) {
+	i += ChatScreen_Instance.chatIndex;
+
+	if (i >= 0 && i < Chat_Log.count) {
+		return StringsBuffer_UNSAFE_Get(&Chat_Log, i);
+	}
+	return String_Empty;
+}
+
+static cc_string ChatScreen_GetStatus(int i)       { return Chat_Status[i]; }
+static cc_string ChatScreen_GetBottomRight(int i)  { return Chat_BottomRight[2 - i]; }
+static cc_string ChatScreen_GetClientStatus(int i) { return Chat_ClientStatus[i]; }
+
+static void ChatScreen_FreeChatFonts(struct ChatScreen* s) {
+	Font_Free(&s->chatFont);
+	Font_Free(&s->announcementFont);
+	Font_Free(&s->bigAnnouncementFont);
+	Font_Free(&s->smallAnnouncementFont);
+}
+
+static cc_bool ChatScreen_ChatUpdateFont(struct ChatScreen* s) {
+	int size = (int)(8  * Gui_GetChatScale());
+	Math_Clamp(size, 8, 64);
+
+	/* don't recreate font if possible */
+	/* TODO: Add function for this, don't use Display_ScaleY (Drawer2D_SameFontSize ??) */
+	if (Display_ScaleY(size) == s->chatFont.size) return false;
+	ChatScreen_FreeChatFonts(s);
+	Font_Make(&s->chatFont, size, FONT_FLAGS_PADDING);
+
+	size = (int)(16 * Gui_GetChatScale());
+	Math_Clamp(size, 8, 64);
+	Font_Make(&s->announcementFont, size, FONT_FLAGS_NONE);
+	size = (int)(24 * Gui_GetChatScale());
+	Math_Clamp(size, 8, 64);
+	Font_Make(&s->bigAnnouncementFont, size, FONT_FLAGS_NONE);
+	size = (int)(8 * Gui_GetChatScale());
+	Math_Clamp(size, 8, 64);
+	Font_Make(&s->smallAnnouncementFont, size, FONT_FLAGS_NONE);
+
+	ChatInputWidget_SetFont(&s->input,        &s->chatFont);
+	TextGroupWidget_SetFont(&s->status,       &s->chatFont);
+	TextGroupWidget_SetFont(&s->bottomRight,  &s->chatFont);
+	TextGroupWidget_SetFont(&s->chat,         &s->chatFont);
+	TextGroupWidget_SetFont(&s->clientStatus, &s->chatFont);
+	return true;
+}
+
+static void ChatScreen_Redraw(struct ChatScreen* s) {
+	TextGroupWidget_RedrawAll(&s->chat);
+	TextWidget_Set(&s->announcement, &Chat_Announcement, &s->announcementFont);
+	TextWidget_Set(&s->bigAnnouncement, &Chat_BigAnnouncement, &s->bigAnnouncementFont);
+	TextWidget_Set(&s->smallAnnouncement, &Chat_SmallAnnouncement, &s->smallAnnouncementFont);
+	TextGroupWidget_RedrawAll(&s->status);
+	TextGroupWidget_RedrawAll(&s->bottomRight);
+	TextGroupWidget_RedrawAll(&s->clientStatus);
+
+	if (s->grabsInput) InputWidget_UpdateText(&s->input.base);
+	SpecialInputWidget_Redraw(&s->altText);
+}
+
+static int ChatScreen_ClampChatIndex(int index) {
+	int maxIndex = Chat_Log.count - Gui.Chatlines;
+	int minIndex = min(0, maxIndex);
+	Math_Clamp(index, minIndex, maxIndex);
+	return index;
+}
+
+static void ChatScreen_ScrollChatBy(struct ChatScreen* s, int delta) {
+	int newIndex = ChatScreen_ClampChatIndex(s->chatIndex + delta);
+	delta = newIndex - s->chatIndex;
+
+	while (delta) {
+		if (delta < 0) {
+			/* scrolling up to oldest */
+			s->chatIndex--; delta++;
+			TextGroupWidget_ShiftDown(&s->chat);
+		} else {
+			/* scrolling down to newest */
+			s->chatIndex++; delta--;
+			TextGroupWidget_ShiftUp(&s->chat);
+		}
+	}
+}
+
+static void ChatScreen_EnterChatInput(struct ChatScreen* s, cc_bool close) {
+	struct InputWidget* input;
+	int defaultIndex;
+
+	s->grabsInput = false;
+	Gui_UpdateInputGrab();
+	OnscreenKeyboard_Close();
+	if (close) InputWidget_Clear(&s->input.base);
+
+	input = &s->input.base;
+	input->OnPressedEnter(input);
+	SpecialInputWidget_SetActive(&s->altText, false);
+	ChatScreen_UpdateChatYOffsets(s);
+
+	/* Reset chat when user has scrolled up in chat history */
+	defaultIndex = Chat_Log.count - Gui.Chatlines;
+	if (s->chatIndex != defaultIndex) {
+		s->chatIndex = defaultIndex;
+		TextGroupWidget_RedrawAll(&s->chat);
+	}
+}
+
+static void ChatScreen_UpdateTexpackStatus(struct ChatScreen* s) {
+	int progress = Http_CheckProgress(TexturePack_ReqID);
+	cc_string msg; char msgBuffer[STRING_SIZE];
+	if (progress == s->lastDownloadStatus) return;
+
+	s->lastDownloadStatus = progress;
+	String_InitArray(msg, msgBuffer);
+
+	if (progress == HTTP_PROGRESS_MAKING_REQUEST) {
+		String_AppendConst(&msg, "&eRetrieving texture pack..");
+	} else if (progress == HTTP_PROGRESS_FETCHING_DATA) {
+		String_AppendConst(&msg, "&eDownloading texture pack");
+	} else if (progress >= 0 && progress <= 100) {
+		String_Format1(&msg, "&eDownloading texture pack (&7%i&e%%)", &progress);
+	}
+	Chat_AddOf(&msg, MSG_TYPE_EXTRASTATUS_1);
+}
+
+static void ChatScreen_ColCodeChanged(void* screen, int code) {
+	struct ChatScreen* s = (struct ChatScreen*)screen;
+	float caretAcc;
+	if (Gfx.LostContext) return;
+
+	SpecialInputWidget_UpdateCols(&s->altText);
+	TextGroupWidget_RedrawAllWithCol(&s->chat,         code);
+	TextGroupWidget_RedrawAllWithCol(&s->status,       code);
+	TextGroupWidget_RedrawAllWithCol(&s->bottomRight,  code);
+	TextGroupWidget_RedrawAllWithCol(&s->clientStatus, code);
+
+	/* Some servers have plugins that redefine colours constantly */
+	/* Preserve caret accumulator so caret blinking stays consistent */
+	caretAcc = s->input.base.caretAccumulator;
+	InputWidget_UpdateText(&s->input.base);
+	s->input.base.caretAccumulator = caretAcc;
+}
+
+static void ChatScreen_ChatReceived(void* screen, const cc_string* msg, int type) {
+	struct ChatScreen* s = (struct ChatScreen*)screen;
+	if (Gfx.LostContext) return;
+	s->dirty = true;
+
+	if (type == MSG_TYPE_NORMAL) {
+		s->chatIndex++;
+		if (!Gui.Chatlines) return;
+		TextGroupWidget_ShiftUp(&s->chat);
+	} else if (type >= MSG_TYPE_STATUS_1 && type <= MSG_TYPE_STATUS_3) {
+		/* Status[0] is for texture pack downloading message */
+		/* Status[1] is for reduced performance mode message */
+		TextGroupWidget_Redraw(&s->status, 2 + (type - MSG_TYPE_STATUS_1));
+	} else if (type >= MSG_TYPE_BOTTOMRIGHT_1 && type <= MSG_TYPE_BOTTOMRIGHT_3) {
+		/* Bottom3 is top most line, so need to redraw index 0 */
+		TextGroupWidget_Redraw(&s->bottomRight, 2 - (type - MSG_TYPE_BOTTOMRIGHT_1));
+	} else if (type == MSG_TYPE_ANNOUNCEMENT) {
+		TextWidget_Set(&s->announcement, msg, &s->announcementFont);
+	} else if (type == MSG_TYPE_BIGANNOUNCEMENT) {
+		TextWidget_Set(&s->bigAnnouncement, msg, &s->bigAnnouncementFont);
+	} else if (type == MSG_TYPE_SMALLANNOUNCEMENT) {
+		TextWidget_Set(&s->smallAnnouncement, msg, &s->smallAnnouncementFont);
+	} else if (type >= MSG_TYPE_CLIENTSTATUS_1 && type <= MSG_TYPE_CLIENTSTATUS_2) {
+		TextGroupWidget_Redraw(&s->clientStatus, type - MSG_TYPE_CLIENTSTATUS_1);
+		ChatScreen_UpdateChatYOffsets(s);
+	} else if (type >= MSG_TYPE_EXTRASTATUS_1 && type <= MSG_TYPE_EXTRASTATUS_2) {
+		/* Status[0] is for texture pack downloading message */
+		/* Status[1] is for reduced performance mode message */
+		TextGroupWidget_Redraw(&s->status, type - MSG_TYPE_EXTRASTATUS_1);
+	} 
+}
+
+
+static void ChatScreen_Update(void* screen, float delta) {
+	struct ChatScreen* s = (struct ChatScreen*)screen;
+	double now = Game.Time;
+
+	/* Destroy announcement texture before even rendering it at all, */
+	/* otherwise changing texture pack shows announcement for one frame */
+	if (s->announcement.tex.ID && now > Chat_AnnouncementReceived + 5) {
+		Elem_Free(&s->announcement);
+	}
+
+	if (s->bigAnnouncement.tex.ID && now > Chat_BigAnnouncementReceived + 5) {
+		Elem_Free(&s->bigAnnouncement);
+	}
+
+	if (s->smallAnnouncement.tex.ID && now > Chat_SmallAnnouncementReceived + 5) {
+		Elem_Free(&s->smallAnnouncement);
+	}
+}
+
+static void ChatScreen_DrawChatBackground(struct ChatScreen* s) {
+	int usedHeight = TextGroupWidget_UsedHeight(&s->chat);
+	int x = s->chat.x;
+	int y = s->chat.y + s->chat.height - usedHeight;
+
+	int width  = max(s->clientStatus.width, s->chat.width);
+	int height = usedHeight + s->clientStatus.height;
+
+	if (height > 0) {
+		PackedCol backCol = PackedCol_Make(0, 0, 0, 127);
+		Gfx_Draw2DFlat( x - s->paddingX,          y - s->paddingY, 
+					width + s->paddingX * 2, height + s->paddingY * 2, backCol);
+	}
+}
+
+static void ChatScreen_DrawChat(struct ChatScreen* s, float delta) {
+	struct Texture tex;
+	double now;
+	int i, logIdx;
+
+	ChatScreen_UpdateTexpackStatus(s);
+	if (!Game_PureClassic) { Elem_Render(&s->status, delta); }
+	Elem_Render(&s->bottomRight, delta);
+	Elem_Render(&s->clientStatus, delta);
+
+	Gfx_SetVertexFormat(VERTEX_FORMAT_TEXTURED);
+	Gfx_BindDynamicVb(s->vb);
+	now = Game.Time;
+
+	if (s->grabsInput) {
+		Widget_Render2(&s->chat, 0);
+	} else {
+		/* Only render recent chat */
+		for (i = 0; i < s->chat.lines; i++) {
+			tex    = s->chat.textures[i];
+			logIdx = s->chatIndex + i;
+			if (!tex.ID) continue;
+
+			if (logIdx < 0 || logIdx >= Chat_Log.count) continue;
+			/* Only draw chat within last 10 seconds */
+			if (Chat_GetLogTime(logIdx) + 10 < now) continue;
+			
+			Gfx_BindTexture(tex.ID);
+			Gfx_DrawVb_IndexedTris_Range(4, i * 4);
+		}
+	}
+
+	Elem_Render(&s->announcement, delta);
+	Elem_Render(&s->bigAnnouncement, delta);
+	Elem_Render(&s->smallAnnouncement, delta);
+
+	if (s->grabsInput) {
+		Elem_Render(&s->input.base, delta);
+		if (s->altText.active) {
+			Elem_Render(&s->altText, delta);
+		}
+
+#ifdef CC_BUILD_TOUCH
+		if (!Gui.TouchUI) return;
+		Gfx_3DS_SetRenderScreen(BOTTOM_SCREEN);
+		Elem_Render(&s->more,   delta);
+		Elem_Render(&s->send,   delta);
+		Elem_Render(&s->cancel, delta);
+		Gfx_3DS_SetRenderScreen(TOP_SCREEN);
+#endif
+	}
+}
+
+static void ChatScreen_ContextLost(void* screen) {
+	struct ChatScreen* s = (struct ChatScreen*)screen;
+	ChatScreen_FreeChatFonts(s);
+	Screen_ContextLost(s);
+
+	Elem_Free(&s->chat);
+	Elem_Free(&s->input.base);
+	Elem_Free(&s->altText);
+	Elem_Free(&s->status);
+	Elem_Free(&s->bottomRight);
+	Elem_Free(&s->clientStatus);
+	Elem_Free(&s->announcement);
+	Elem_Free(&s->bigAnnouncement);
+	Elem_Free(&s->smallAnnouncement);
+
+#ifdef CC_BUILD_TOUCH
+	Elem_Free(&s->more);
+	Elem_Free(&s->send);
+	Elem_Free(&s->cancel);
+#endif
+}
+
+static void ChatScreen_ContextRecreated(void* screen) {
+	struct ChatScreen* s = (struct ChatScreen*)screen;
+	struct FontDesc font;
+	ChatScreen_ChatUpdateFont(s);
+	ChatScreen_Redraw(s);
+	Screen_UpdateVb(s);
+
+#ifdef CC_BUILD_TOUCH
+	if (!Gui.TouchUI) return;
+	Gui_MakeTitleFont(&font);
+	ButtonWidget_SetConst(&s->more,   "More",   &font);
+	ButtonWidget_SetConst(&s->send,   "Send",   &font);
+	ButtonWidget_SetConst(&s->cancel, "Cancel", &font);
+	Font_Free(&font);
+#endif
+}
+
+static int ChatScreen_CalcMaxVertices(void* screen) {
+	struct ChatScreen* s = (struct ChatScreen*)screen;
+	struct TextGroupWidget* chat = &s->chat;
+	/* In case chatlines is 0 */
+	return max(4, chat->VTABLE->GetMaxVertices(chat));
+}
+
+static void ChatScreen_BuildMesh(void* screen) {
+	struct ChatScreen* s = (struct ChatScreen*)screen;
+	struct VertexTextured* data;
+	struct VertexTextured** ptr;
+
+	data = Screen_LockVb(s);
+	ptr  = &data;
+
+	Widget_BuildMesh(&s->chat, ptr);
+	Gfx_UnlockDynamicVb(s->vb);
+}
+
+static void ChatScreen_Layout(void* screen) {
+	struct ChatScreen* s = (struct ChatScreen*)screen;
+	if (ChatScreen_ChatUpdateFont(s)) ChatScreen_Redraw(s);
+
+	s->paddingX = Display_ScaleX(5);
+	s->paddingY = Display_ScaleY(5);
+
+	Widget_SetLocation(&s->input.base,   ANCHOR_MIN, ANCHOR_MAX,  5, 5);
+	Widget_SetLocation(&s->altText,      ANCHOR_MIN, ANCHOR_MAX,  5, 5);
+	Widget_SetLocation(&s->status,       ANCHOR_MAX, ANCHOR_MIN,  0, 0);
+	Widget_SetLocation(&s->bottomRight,  ANCHOR_MAX, ANCHOR_MAX,  0, 0);
+	Widget_SetLocation(&s->chat,         ANCHOR_MIN, ANCHOR_MAX, 10, 0);
+	Widget_SetLocation(&s->clientStatus, ANCHOR_MIN, ANCHOR_MAX, 10, 0);
+	ChatScreen_UpdateChatYOffsets(s);
+
+	/* Can't use Widget_SetLocation because it DPI scales input */
+	s->bottomRight.yOffset = HUDScreen_LayoutHotbar() + Display_ScaleY(15);
+	Widget_Layout(&s->bottomRight);
+
+	Widget_SetLocation(&s->announcement, ANCHOR_CENTRE, ANCHOR_CENTRE, 0, 0);
+	s->announcement.yOffset = -Window_UI.Height / 4;
+	Widget_Layout(&s->announcement);
+
+	Widget_SetLocation(&s->bigAnnouncement, ANCHOR_CENTRE, ANCHOR_CENTRE, 0, 0);
+	s->bigAnnouncement.yOffset = -Window_UI.Height / 16;
+	Widget_Layout(&s->bigAnnouncement);
+
+	Widget_SetLocation(&s->smallAnnouncement, ANCHOR_CENTRE, ANCHOR_CENTRE, 0, 0);
+	s->smallAnnouncement.yOffset = Window_UI.Height / 20;
+	Widget_Layout(&s->smallAnnouncement);
+
+#ifdef CC_BUILD_TOUCH
+	if (Window_Main.SoftKeyboard == SOFT_KEYBOARD_SHIFT) {
+		Widget_SetLocation(&s->send,   ANCHOR_MAX, ANCHOR_MAX, 10,  60);
+		Widget_SetLocation(&s->cancel, ANCHOR_MAX, ANCHOR_MAX, 10,  10);
+		Widget_SetLocation(&s->more,   ANCHOR_MAX, ANCHOR_MAX, 10, 110);
+	} else {
+		Widget_SetLocation(&s->send,   ANCHOR_MAX, ANCHOR_MIN, 10,  10);
+		Widget_SetLocation(&s->cancel, ANCHOR_MAX, ANCHOR_MIN, 10,  60);
+		Widget_SetLocation(&s->more,   ANCHOR_MAX, ANCHOR_MIN, 10, 110);
+	}
+#endif
+}
+
+static int ChatScreen_KeyPress(void* screen, char keyChar) {
+	struct ChatScreen* s = (struct ChatScreen*)screen;
+	if (!s->grabsInput) return false;
+
+	if (s->suppressNextPress) {
+		s->suppressNextPress = false;
+		return false;
+	}
+
+	InputWidget_Append(&s->input.base, keyChar);
+	return true;
+}
+
+static int ChatScreen_TextChanged(void* screen, const cc_string* str) {
+	struct ChatScreen* s = (struct ChatScreen*)screen;
+	if (!s->grabsInput) return false;
+
+	InputWidget_SetText(&s->input.base, str);
+	return true;
+}
+
+static int ChatScreen_KeyDown(void* screen, int key) {
+	static const cc_string slash = String_FromConst("/");
+	struct ChatScreen* s = (struct ChatScreen*)screen;
+	int playerListKey    = KeyBind_Mappings[BIND_TABLIST].button1;
+	cc_bool handlesList  = playerListKey != CCKEY_TAB || !Gui.TabAutocomplete || !s->grabsInput;
+
+	if (InputBind_Claims(BIND_TABLIST, key) && handlesList) {
+		if (!tablist_active && !Server.IsSinglePlayer) {
+			TabListOverlay_Show(false);
+		}
+		return true;
+	}
+
+	s->suppressNextPress = false;
+	/* Handle chat text input */
+	if (s->grabsInput) {
+#ifdef CC_BUILD_WEB
+		/* See reason for this in HandleInputUp */
+		if (InputBind_Claims(BIND_SEND_CHAT, key) || key == CCKEY_KP_ENTER) {
+			ChatScreen_EnterChatInput(s, false);
+#else
+		if (InputBind_Claims(BIND_SEND_CHAT, key) || key == CCKEY_KP_ENTER || Input_IsEscapeButton(key)) {
+			ChatScreen_EnterChatInput(s, Input_IsEscapeButton(key));
+#endif
+		} else if (key == CCKEY_PAGEUP) {
+			ChatScreen_ScrollChatBy(s, -Gui.Chatlines);
+		} else if (key == CCKEY_PAGEDOWN) {
+			ChatScreen_ScrollChatBy(s, +Gui.Chatlines);
+		} else if (key == CCWHEEL_UP) {
+			ChatScreen_ScrollChatBy(s, -1);
+		} else if (key == CCWHEEL_DOWN) {
+			ChatScreen_ScrollChatBy(s, +1);
+		} else {
+			Elem_HandlesKeyDown(&s->input.base, key);
+		}
+		return key < CCKEY_F1 || key > CCKEY_F24;
+	}
+
+	if (InputBind_Claims(BIND_CHAT, key)) {
+		ChatScreen_OpenInput(&String_Empty);
+	} else if (key == CCKEY_SLASH) {
+		ChatScreen_OpenInput(&slash);
+	} else if (InputBind_Claims(BIND_INVENTORY, key)) {
+		InventoryScreen_Show();
+	} else {
+		return false;
+	}
+	return true;
+}
+
+static void ChatScreen_ToggleAltInput(struct ChatScreen* s) {
+	SpecialInputWidget_SetActive(&s->altText, !s->altText.active);
+	ChatScreen_UpdateChatYOffsets(s);
+}
+
+static void ChatScreen_KeyUp(void* screen, int key) {
+	struct ChatScreen* s = (struct ChatScreen*)screen;
+	if (!s->grabsInput || (struct Screen*)s != Gui.InputGrab) return;
+
+#ifdef CC_BUILD_WEB
+	/* See reason for this in HandleInputUp */
+	if (key == CCKEY_ESCAPE) ChatScreen_EnterChatInput(s, true);
+#endif
+
+	if (Server.SupportsFullCP437 && InputBind_Claims(BIND_EXT_INPUT, key)) {
+		if (!Window_Main.Focused) return;
+		ChatScreen_ToggleAltInput(s);
+	}
+}
+
+static int ChatScreen_MouseScroll(void* screen, float delta) {
+	struct ChatScreen* s = (struct ChatScreen*)screen;
+	return s->grabsInput;
+}
+
+static int ChatScreen_PointerDown(void* screen, int id, int x, int y) {
+	cc_string text; char textBuffer[STRING_SIZE * 4];
+	struct ChatScreen* s = (struct ChatScreen*)screen;
+	int height, chatY, i;
+	if (Game_HideGui) return false;
+
+	if (!s->grabsInput) {
+		if (!Gui_TouchUI) return false;
+		String_InitArray(text, textBuffer);
+
+		/* Should be able to click on links with touch */
+		i = TextGroupWidget_GetSelected(&s->chat, &text, x, y);
+		if (!Utils_IsUrlPrefix(&text)) return false;
+
+		if (Chat_GetLogTime(s->chatIndex + i) + 10 < Game.Time) return false;
+		UrlWarningOverlay_Show(&text); return TOUCH_TYPE_GUI;
+	}
+
+#ifdef CC_BUILD_TOUCH
+	if (Gui.TouchUI) {
+		if (Widget_Contains(&s->send, x, y)) {
+			ChatScreen_EnterChatInput(s, false); return TOUCH_TYPE_GUI;
+		}
+		if (Widget_Contains(&s->cancel, x, y)) {
+			ChatScreen_EnterChatInput(s, true); return TOUCH_TYPE_GUI;
+		}
+		if (Widget_Contains(&s->more, x, y)) {
+			ChatScreen_ToggleAltInput(s); return TOUCH_TYPE_GUI;
+		}
+	}
+#endif
+
+	if (!Widget_Contains(&s->chat, x, y)) {
+		if (s->altText.active && Widget_Contains(&s->altText, x, y)) {
+			Elem_HandlesPointerDown(&s->altText, id, x, y);
+			ChatScreen_UpdateChatYOffsets(s);
+			return TOUCH_TYPE_GUI;
+		}
+		Elem_HandlesPointerDown(&s->input.base, id, x, y);
+		return TOUCH_TYPE_GUI;
+	}
+
+	height = TextGroupWidget_UsedHeight(&s->chat);
+	chatY  = s->chat.y + s->chat.height - height;
+	if (!Gui_Contains(s->chat.x, chatY, s->chat.width, height, x, y)) return false;
+
+	String_InitArray(text, textBuffer);
+	TextGroupWidget_GetSelected(&s->chat, &text, x, y);
+	if (!text.length) return false;
+
+	if (Utils_IsUrlPrefix(&text) && Process_OpenSupported) {
+		UrlWarningOverlay_Show(&text);
+	} else if (Gui.ClickableChat) {
+		ChatScreen_AppendInput(&text);
+	}
+	return TOUCH_TYPE_GUI;
+}
+
+static void ChatScreen_Init(void* screen) {
+	struct ChatScreen* s = (struct ChatScreen*)screen;
+	ChatInputWidget_Create(&s->input);
+	s->input.base.OnTextChanged = ChatScreen_OnInputTextChanged;
+	SpecialInputWidget_Create(&s->altText, &s->chatFont, &s->input.base);
+
+	TextGroupWidget_Create(&s->status, CHAT_MAX_STATUS,
+							s->statusTextures, ChatScreen_GetStatus);
+	TextGroupWidget_Create(&s->bottomRight, CHAT_MAX_BOTTOMRIGHT, 
+							s->bottomRightTextures, ChatScreen_GetBottomRight);
+	TextGroupWidget_Create(&s->chat, Gui.Chatlines,
+							s->chatTextures, ChatScreen_GetChat);
+	TextGroupWidget_Create(&s->clientStatus, CHAT_MAX_CLIENTSTATUS,
+							s->clientStatusTextures, ChatScreen_GetClientStatus);
+	TextWidget_Init(&s->announcement);
+	TextWidget_Init(&s->bigAnnouncement);
+	TextWidget_Init(&s->smallAnnouncement);
+
+	s->status.collapsible[0]       = true; /* Texture pack downloading status */
+	s->status.collapsible[1]       = true; /* Reduced performance mode status */
+	s->clientStatus.collapsible[0] = true;
+	s->clientStatus.collapsible[1] = true;
+
+	s->chat.underlineUrls = !Game_ClassicMode;
+	s->chatIndex = Chat_Log.count - Gui.Chatlines;
+
+	Event_Register_(&ChatEvents.ChatReceived,   s, ChatScreen_ChatReceived);
+	Event_Register_(&ChatEvents.ColCodeChanged, s, ChatScreen_ColCodeChanged);
+	
+	s->maxVertices = ChatScreen_CalcMaxVertices(s);
+	
+	/* For dual screen builds, chat is still rendered on the main game screen */
+	s->input.base.flags   |= WIDGET_FLAG_MAINSCREEN;
+	s->altText.flags      |= WIDGET_FLAG_MAINSCREEN;
+	s->status.flags       |= WIDGET_FLAG_MAINSCREEN;
+	s->bottomRight.flags  |= WIDGET_FLAG_MAINSCREEN;
+	s->chat.flags         |= WIDGET_FLAG_MAINSCREEN;
+	s->clientStatus.flags |= WIDGET_FLAG_MAINSCREEN;
+
+	s->bottomRight.flags       |= WIDGET_FLAG_MAINSCREEN;
+	s->announcement.flags      |= WIDGET_FLAG_MAINSCREEN;
+	s->bigAnnouncement.flags   |= WIDGET_FLAG_MAINSCREEN;
+	s->smallAnnouncement.flags |= WIDGET_FLAG_MAINSCREEN;
+
+#ifdef CC_BUILD_TOUCH
+	ButtonWidget_Init(&s->send,   100, NULL);
+	ButtonWidget_Init(&s->cancel, 100, NULL);
+	ButtonWidget_Init(&s->more,   100, NULL);
+#endif
+}
+
+static void ChatScreen_Render(void* screen, float delta) {
+	struct ChatScreen* s = (struct ChatScreen*)screen;
+	Gfx_3DS_SetRenderScreen(TOP_SCREEN);
+
+	if (Game_HideGui && s->grabsInput) {
+		Elem_Render(&s->input.base, delta);
+	}
+	if (!Game_HideGui) {
+		if (s->grabsInput && !Gui.ClassicChat) {
+			ChatScreen_DrawChatBackground(s);
+		}
+
+		ChatScreen_DrawChat(s, delta);
+	}
+	Gfx_3DS_SetRenderScreen(BOTTOM_SCREEN);
+}
+
+static void ChatScreen_Free(void* screen) {
+	struct ChatScreen* s = (struct ChatScreen*)screen;
+	Event_Unregister_(&ChatEvents.ChatReceived,   s, ChatScreen_ChatReceived);
+	Event_Unregister_(&ChatEvents.ColCodeChanged, s, ChatScreen_ColCodeChanged);
+}
+
+static const struct ScreenVTABLE ChatScreen_VTABLE = {
+	ChatScreen_Init,        ChatScreen_Update, ChatScreen_Free,
+	ChatScreen_Render,      ChatScreen_BuildMesh,
+	ChatScreen_KeyDown,     ChatScreen_KeyUp,  ChatScreen_KeyPress, ChatScreen_TextChanged,
+	ChatScreen_PointerDown, Screen_PointerUp,  Screen_FPointer,     ChatScreen_MouseScroll,
+	ChatScreen_Layout, ChatScreen_ContextLost, ChatScreen_ContextRecreated
+};
+void ChatScreen_Show(void) {
+	struct ChatScreen* s  = &ChatScreen_Instance;
+	s->lastDownloadStatus = HTTP_PROGRESS_NOT_WORKING_ON;
+
+	s->VTABLE = &ChatScreen_VTABLE;
+	Gui_Chat  = s;
+	Gui_Add((struct Screen*)s, GUI_PRIORITY_CHAT);
+}
+
+void ChatScreen_OpenInput(const cc_string* text) {
+	struct ChatScreen* s = &ChatScreen_Instance;
+	struct OpenKeyboardArgs args;
+	s->suppressNextPress = true;
+	s->grabsInput        = true;
+
+	Gui_UpdateInputGrab();
+	String_Copy(&s->input.base.text, text);
+
+	OpenKeyboardArgs_Init(&args, text, KEYBOARD_TYPE_TEXT | KEYBOARD_FLAG_SEND);
+	args.placeholder = "Enter chat";
+	args.multiline   = true;
+	OnscreenKeyboard_Open(&args);
+
+	Widget_SetDisabled(&s->input.base, args.opaque);
+	InputWidget_UpdateText(&s->input.base);
+}
+
+void ChatScreen_AppendInput(const cc_string* text) {
+	struct ChatScreen* s = &ChatScreen_Instance;
+	InputWidget_AppendText(&s->input.base, text);
+}
+
+void ChatScreen_SetChatlines(int lines) {
+	struct ChatScreen* s = &ChatScreen_Instance;
+	Elem_Free(&s->chat);
+	s->chatIndex += s->chat.lines - lines;
+	s->chat.lines = lines;
+	TextGroupWidget_RedrawAll(&s->chat);
+
+	s->maxVertices = ChatScreen_CalcMaxVertices(s);
+	Screen_UpdateVb(s);
+	s->dirty = true;
+}
+
+
+/*########################################################################################################################*
+*-----------------------------------------------------InventoryScreen-----------------------------------------------------*
+*#########################################################################################################################*/
+static struct InventoryScreen {
+	Screen_Body
+	struct FontDesc font;
+	struct TableWidget table;
+	struct TextWidget title;
+	cc_bool releasedInv, deferredSelect;
+} InventoryScreen;
+
+static struct Widget* inventory_widgets[2];
+
+
+static void InventoryScreen_GetTitleText(cc_string* desc, BlockID block) {
+	cc_string name;
+	int block_ = block;
+	if (Game_PureClassic) { String_AppendConst(desc, "Select block"); return; }
+	if (block == BLOCK_AIR) return;
+
+	name = Block_UNSAFE_GetName(block);
+	String_AppendString(desc, &name);
+	if (Game_ClassicMode) return;
+
+	String_Format1(desc, " (ID %i&f", &block_);
+	if (!Blocks.CanPlace[block])  { String_AppendConst(desc,  ", place &cNo&f"); }
+	if (!Blocks.CanDelete[block]) { String_AppendConst(desc, ", delete &cNo&f"); }
+	String_Append(desc, ')');
+}
+
+static void InventoryScreen_UpdateTitle(struct InventoryScreen* s, BlockID block) {
+	cc_string desc; char descBuffer[STRING_SIZE * 2];
+
+	String_InitArray(desc, descBuffer);
+	InventoryScreen_GetTitleText(&desc, block);
+	TextWidget_Set(&s->title, &desc, &s->font);
+	s->dirty = true;
+}
+
+static void InventoryScreen_OnUpdateTitle(BlockID block) {
+	InventoryScreen_UpdateTitle(&InventoryScreen, block);
+}
+
+
+static void InventoryScreen_OnBlockChanged(void* screen) {
+	struct InventoryScreen* s = (struct InventoryScreen*)screen;
+	TableWidget_OnInventoryChanged(&s->table);
+}
+
+static void InventoryScreen_NeedRedrawing(void* screen) {
+	struct InventoryScreen* s = (struct InventoryScreen*)screen;
+	s->dirty = true;
+}
+
+static void InventoryScreen_ContextLost(void* screen) {
+	struct InventoryScreen* s = (struct InventoryScreen*)screen;
+	Font_Free(&s->font);
+	Screen_ContextLost(s);
+	s->table.vb = 0;
+}
+
+static void InventoryScreen_ContextRecreated(void* screen) {
+	struct InventoryScreen* s = (struct InventoryScreen*)screen;
+	Screen_UpdateVb(s);
+	s->table.vb = s->vb;
+
+	Gui_MakeBodyFont(&s->font);
+	TableWidget_RecreateTitle(&s->table, true);
+}
+
+static void InventoryScreen_MoveToSelected(struct InventoryScreen* s) {
+	struct TableWidget* table = &s->table;
+	s->deferredSelect = false;
+
+	if (Game_ClassicMode) {
+		/* Accuracy: Original classic preserves selected block across inventory menu opens */
+		TableWidget_SetToIndex(table, table->selectedIndex);
+		TableWidget_RecreateTitle(table, true);
+	} else {
+		TableWidget_SetToBlock(table, Inventory_SelectedBlock);
+
+		if (table->selectedIndex == -1) {
+			/* Hidden block in inventory - display title for it still */
+			InventoryScreen_OnUpdateTitle(Inventory_SelectedBlock);
+		} else {
+			TableWidget_RecreateTitle(table, true);
+		}
+	}
+}
+
+static void InventoryScreen_Init(void* screen) {
+	struct InventoryScreen* s = (struct InventoryScreen*)screen;
+	s->widgets     = inventory_widgets;
+	s->numWidgets  = 0;
+	s->maxWidgets  = Array_Elems(inventory_widgets);
+	
+	TextWidget_Add(s,  &s->title);
+	TableWidget_Add(s, &s->table, 22 * Options_GetFloat(OPT_INV_SCROLLBAR_SCALE, 0, 10, 1));
+	s->table.blocksPerRow = Inventory.BlocksPerRow;
+	s->table.UpdateTitle   = InventoryScreen_OnUpdateTitle;
+	TableWidget_RecreateBlocks(&s->table);
+
+	/* Can't immediately move to selected here, because cursor grabbed  */
+	/*  status might be toggled *after* InventoryScreen_Init() is called */
+	/* That causes the cursor to be moved back to the middle of the window */
+	s->deferredSelect = true;
+
+	Event_Register_(&TextureEvents.AtlasChanged,     s, InventoryScreen_NeedRedrawing);
+	Event_Register_(&BlockEvents.PermissionsChanged, s, InventoryScreen_OnBlockChanged);
+	Event_Register_(&BlockEvents.BlockDefChanged,    s, InventoryScreen_OnBlockChanged);
+
+	s->maxVertices = Screen_CalcDefaultMaxVertices(s);
+}
+
+static void InventoryScreen_Free(void* screen) {
+	struct InventoryScreen* s = (struct InventoryScreen*)screen;
+
+	Event_Unregister_(&TextureEvents.AtlasChanged,     s, InventoryScreen_NeedRedrawing);
+	Event_Unregister_(&BlockEvents.PermissionsChanged, s, InventoryScreen_OnBlockChanged);
+	Event_Unregister_(&BlockEvents.BlockDefChanged,    s, InventoryScreen_OnBlockChanged);
+}
+
+static void InventoryScreen_Update(void* screen, float delta) {
+	struct InventoryScreen* s = (struct InventoryScreen*)screen;
+	if (s->deferredSelect) InventoryScreen_MoveToSelected(s);
+}
+
+static void InventoryScreen_Render(void* screen, float delta) {
+	struct InventoryScreen* s = (struct InventoryScreen*)screen;
+	Widget_Render2(&s->table, TEXTWIDGET_MAX);
+	Widget_Render2(&s->title,              0);
+}
+
+static void InventoryScreen_Layout(void* screen) {
+	struct InventoryScreen* s = (struct InventoryScreen*)screen;
+	s->table.scale = Gui_GetInventoryScale();
+	Widget_SetLocation(&s->table, ANCHOR_CENTRE, ANCHOR_CENTRE, 0, 0);
+
+	Widget_SetLocation(&s->title, ANCHOR_CENTRE, ANCHOR_MIN, 0, 0);
+	/* use Table(Y) directly instead of s->title->height ??? */
+	s->title.yOffset = s->table.y - s->title.height - 3;
+	Widget_Layout(&s->title); /* Needed for yOffset */
+}
+
+static int InventoryScreen_KeyDown(void* screen, int key) {
+	struct InventoryScreen* s = (struct InventoryScreen*)screen;
+	struct TableWidget* table = &s->table;
+
+	/* Accuracy: Original classic doesn't close inventory menu when B is pressed */
+	if (InputBind_Claims(BIND_INVENTORY, key) && s->releasedInv && !Game_ClassicMode) {
+		Gui_Remove((struct Screen*)s);
+	} else if (Input_IsEnterButton(key) && table->selectedIndex != -1) {
+		Inventory_SetSelectedBlock(table->blocks[table->selectedIndex]);
+		Gui_Remove((struct Screen*)s);
+	} else if (Elem_HandlesKeyDown(table, key)) {
+	} else {
+		return Elem_HandlesKeyDown(&HUDScreen_Instance.hotbar, key);
+	}
+	return true;
+}
+
+static cc_bool InventoryScreen_IsHotbarActive(void) {
+	struct Screen* grabbed = Gui.InputGrab;
+	/* Only toggle hotbar when inventory or no grab screen is open */
+	return !grabbed || grabbed == (struct Screen*)&InventoryScreen;
+}
+
+static void InventoryScreen_KeyUp(void* screen, int key) {
+	struct InventoryScreen* s = (struct InventoryScreen*)screen;
+	if (InputBind_Claims(BIND_INVENTORY, key)) s->releasedInv = true;
+}
+
+static int InventoryScreen_PointerDown(void* screen, int id, int x, int y) {
+	struct InventoryScreen* s = (struct InventoryScreen*)screen;
+	struct TableWidget* table = &s->table;
+	cc_bool handled, hotbar;
+
+	if (table->scroll.draggingId == id) return TOUCH_TYPE_GUI;
+	if (HUDscreen_PointerDown(Gui_HUD, id, x, y)) return TOUCH_TYPE_GUI;
+	handled = Elem_HandlesPointerDown(table, id, x, y);
+
+	if (!handled || table->pendingClose) {
+		hotbar = Input_IsCtrlPressed() || Input_IsShiftPressed();
+		if (!hotbar) Gui_Remove((struct Screen*)s);
+	}
+	return TOUCH_TYPE_GUI;
+}
+
+static void InventoryScreen_PointerUp(void* screen, int id, int x, int y) {
+	struct InventoryScreen* s = (struct InventoryScreen*)screen;
+	Elem_OnPointerUp(&s->table, id, x, y);
+}
+
+static int InventoryScreen_PointerMove(void* screen, int id, int x, int y) {
+	struct InventoryScreen* s = (struct InventoryScreen*)screen;
+	return Elem_HandlesPointerMove(&s->table, id, x, y);
+}
+
+static int InventoryScreen_MouseScroll(void* screen, float delta) {
+	struct InventoryScreen* s = (struct InventoryScreen*)screen;
+
+	cc_bool hotbar = Input_IsAltPressed() || Input_IsCtrlPressed() || Input_IsShiftPressed();
+	if (hotbar) return false;
+	return Elem_HandlesMouseScroll(&s->table, delta);
+}
+
+static int InventoryScreen_PadAxis(void* screen, int axis, float x, float y) {
+	struct InventoryScreen* s = (struct InventoryScreen*)screen;
+
+	return Elem_HandlesPadAxis(&s->table, axis, x, y);
+}
+
+static const struct ScreenVTABLE InventoryScreen_VTABLE = {
+	InventoryScreen_Init,        InventoryScreen_Update,    InventoryScreen_Free,
+	InventoryScreen_Render,      Screen_BuildMesh,
+	InventoryScreen_KeyDown,     InventoryScreen_KeyUp,     Screen_TKeyPress,            Screen_TText,
+	InventoryScreen_PointerDown, InventoryScreen_PointerUp, InventoryScreen_PointerMove, InventoryScreen_MouseScroll,
+	InventoryScreen_Layout,  InventoryScreen_ContextLost, InventoryScreen_ContextRecreated,
+	InventoryScreen_PadAxis
+};
+void InventoryScreen_Show(void) {
+	struct InventoryScreen* s = &InventoryScreen;
+	s->grabsInput = true;
+	s->closable   = true;
+
+	s->VTABLE = &InventoryScreen_VTABLE;
+	Gui_Add((struct Screen*)s, GUI_PRIORITY_INVENTORY);
+}
+
+
+/*########################################################################################################################*
+*------------------------------------------------------LoadingScreen------------------------------------------------------*
+*#########################################################################################################################*/
+static struct LoadingScreen {
+	Screen_Body
+	struct FontDesc font;
+	float progress; 
+	int rows;
+	
+	int progX, progY, progWidth, progHeight;
+	struct TextWidget title, message;
+	cc_string titleStr, messageStr;
+	const char* lastState;
+
+	char _titleBuffer[STRING_SIZE];
+	char _messageBuffer[STRING_SIZE];
+} LoadingScreen;
+
+static struct Widget* loading_widgets[2];
+#define LOADING_TILE_SIZE 64
+
+static void LoadingScreen_SetTitle(struct LoadingScreen* s) {
+	TextWidget_Set(&s->title, &s->titleStr, &s->font);
+	s->dirty = true;
+}
+static void LoadingScreen_SetMessage(struct LoadingScreen* s) {
+	TextWidget_Set(&s->message, &s->messageStr, &s->font);
+	s->dirty = true;
+}
+
+static void LoadingScreen_CalcMaxVertices(struct LoadingScreen* s) {
+	s->rows = Math_CeilDiv(Window_UI.Height, LOADING_TILE_SIZE);
+	s->maxVertices = Screen_CalcDefaultMaxVertices(s) + s->rows * 4;
+}
+
+static void LoadingScreen_Layout(void* screen) {
+	struct LoadingScreen* s = (struct LoadingScreen*)screen;
+	int oldRows, y;
+	Widget_SetLocation(&s->title,   ANCHOR_CENTRE, ANCHOR_CENTRE, 0, -31);
+	Widget_SetLocation(&s->message, ANCHOR_CENTRE, ANCHOR_CENTRE, 0,  17);
+	y = Display_ScaleY(34);
+
+	s->progWidth  = Display_ScaleX(200);
+	s->progX      = Gui_CalcPos(ANCHOR_CENTRE, 0, s->progWidth,  Window_UI.Width);
+	s->progHeight = Display_ScaleY(4);
+	s->progY      = Gui_CalcPos(ANCHOR_CENTRE, y, s->progHeight, Window_UI.Height);
+
+	oldRows = s->rows;
+	LoadingScreen_CalcMaxVertices(s);
+	if (oldRows == s->rows) return;
+	Screen_UpdateVb(s);
+}
+
+static void LoadingScreen_ContextLost(void* screen) {
+	struct LoadingScreen* s = (struct LoadingScreen*)screen;
+	Font_Free(&s->font);
+	Screen_ContextLost(screen);
+}
+
+static void LoadingScreen_ContextRecreated(void* screen) {
+	struct LoadingScreen* s = (struct LoadingScreen*)screen;
+	Gui_MakeBodyFont(&s->font);
+	LoadingScreen_SetTitle(s);
+	LoadingScreen_SetMessage(s);
+	Screen_UpdateVb(s);
+}
+
+static void LoadingScreen_BuildMesh(void* screen) {
+	struct LoadingScreen* s = (struct LoadingScreen*)screen;
+	struct VertexTextured* data;
+	struct VertexTextured** ptr;
+	struct Texture tex;
+	TextureLoc loc;
+	int atlasIndex, i;
+
+	data = Screen_LockVb(s);
+	ptr  = &data;
+
+	loc       = Block_Tex(BLOCK_DIRT, FACE_YMAX);
+	Tex_SetRect(tex, 0,0, Window_UI.Width,LOADING_TILE_SIZE);
+	tex.uv    = Atlas1D_TexRec(loc, 1, &atlasIndex);
+	tex.uv.u2 = (float)Window_UI.Width / LOADING_TILE_SIZE;
+	
+	for (i = 0; i < s->rows; i++) {
+		tex.y = i * LOADING_TILE_SIZE;
+		Gfx_Make2DQuad(&tex, PackedCol_Make(64, 64, 64, 255), ptr);
+	}
+
+	Widget_BuildMesh(&s->title,   ptr);
+	Widget_BuildMesh(&s->message, ptr);
+	Gfx_UnlockDynamicVb(s->vb);
+}
+
+static void LoadingScreen_MapLoading(void* screen, float progress) {
+	((struct LoadingScreen*)screen)->progress = progress;
+}
+
+static void LoadingScreen_MapLoaded(void* screen) {
+	Gui_Remove((struct Screen*)screen);
+}
+
+static void LoadingScreen_Init(void* screen) {
+	struct LoadingScreen* s = (struct LoadingScreen*)screen;
+	s->widgets     = loading_widgets;
+	s->numWidgets  = 0;
+	s->maxWidgets  = Array_Elems(loading_widgets);
+
+	TextWidget_Add(s, &s->title);
+	TextWidget_Add(s, &s->message);
+
+	LoadingScreen_CalcMaxVertices(s);
+	Gfx_SetFog(false);
+	Event_Register_(&WorldEvents.Loading,   s, LoadingScreen_MapLoading);
+	Event_Register_(&WorldEvents.MapLoaded, s, LoadingScreen_MapLoaded);
+}
+
+static void LoadingScreen_Render(void* screen, float delta) {
+	struct LoadingScreen* s = (struct LoadingScreen*)screen;
+	int offset, filledWidth;
+	TextureLoc loc;
+
+	Gfx_SetVertexFormat(VERTEX_FORMAT_TEXTURED);
+	Gfx_BindDynamicVb(s->vb);
+
+	/* Draw background dirt */
+	offset = 0;
+	if (s->rows) {
+		loc = Block_Tex(BLOCK_DIRT, FACE_YMAX);
+		Gfx_BindTexture(Atlas1D.TexIds[Atlas1D_Index(loc)]);
+		Gfx_DrawVb_IndexedTris(s->rows * 4);
+		offset = s->rows * 4;
+	}
+
+	offset = Widget_Render2(&s->title,   offset);
+	offset = Widget_Render2(&s->message, offset);
+
+	filledWidth = (int)(s->progWidth * s->progress);
+	Gfx_Draw2DFlat(s->progX, s->progY, s->progWidth, 
+					s->progHeight, PackedCol_Make(128, 128, 128, 255));
+	Gfx_Draw2DFlat(s->progX, s->progY, filledWidth,  
+					s->progHeight, PackedCol_Make(128, 255, 128, 255));
+}
+
+static void LoadingScreen_Free(void* screen) {
+	struct LoadingScreen* s = (struct LoadingScreen*)screen;
+	Event_Unregister_(&WorldEvents.Loading,   s, LoadingScreen_MapLoading);
+	Event_Unregister_(&WorldEvents.MapLoaded, s, LoadingScreen_MapLoaded);
+}
+
+CC_NOINLINE static void LoadingScreen_ShowCommon(const cc_string* title, const cc_string* message) {
+	struct LoadingScreen* s = &LoadingScreen;
+	s->lastState = NULL;
+	s->progress  = 0.0f;
+
+	String_InitArray(s->titleStr,   s->_titleBuffer);
+	String_AppendString(&s->titleStr,   title);
+	String_InitArray(s->messageStr, s->_messageBuffer);
+	String_AppendString(&s->messageStr, message);
+	
+	s->grabsInput  = true;
+	s->blocksWorld = true;
+	Gui_Add((struct Screen*)s, 
+		Game_ClassicMode ? GUI_PRIORITY_OLDLOADING : GUI_PRIORITY_LOADING);
+}
+
+static const struct ScreenVTABLE LoadingScreen_VTABLE = {
+	LoadingScreen_Init,   Screen_NullUpdate, LoadingScreen_Free, 
+	LoadingScreen_Render, LoadingScreen_BuildMesh,
+	Screen_TInput,        Screen_InputUp,    Screen_TKeyPress,   Screen_TText,
+	Screen_TPointer,      Screen_PointerUp,  Screen_TPointer,    Screen_TMouseScroll,
+	LoadingScreen_Layout, LoadingScreen_ContextLost, LoadingScreen_ContextRecreated
+};
+void LoadingScreen_Show(const cc_string* title, const cc_string* message) {
+	LoadingScreen.VTABLE = &LoadingScreen_VTABLE;
+	LoadingScreen_ShowCommon(title, message);
+}
+
+
+/*########################################################################################################################*
+*--------------------------------------------------GeneratingMapScreen----------------------------------------------------*
+*#########################################################################################################################*/
+static void GeneratingScreen_AtlasChanged(void* obj) {
+	LoadingScreen.dirty = true; /* Dirt texture may have changed */
+}
+
+static void GeneratingScreen_Init(void* screen) {
+	LoadingScreen_Init(screen);
+	Event_Register_(&TextureEvents.AtlasChanged,   NULL, GeneratingScreen_AtlasChanged);
+}
+static void GeneratingScreen_Free(void* screen) {
+	LoadingScreen_Free(screen);
+	Event_Unregister_(&TextureEvents.AtlasChanged, NULL, GeneratingScreen_AtlasChanged);
+}
+
+static void GeneratingScreen_EndGeneration(void) {
+	struct LocationUpdate update;
+	World_SetNewMap(Gen_Blocks, World.Width, World.Height, World.Length);
+	if (!Gen_Blocks) { Chat_AddRaw("&cFailed to generate the map."); return; }
+
+	Gen_Blocks = NULL;
+	World.Seed = Gen_Seed;
+
+	LocalPlayer_CalcDefaultSpawn(Entities.CurPlayer, &update);
+	LocalPlayers_MoveToSpawn(&update);
+}
+
+static void GeneratingScreen_Update(void* screen, float delta) {
+	struct LoadingScreen* s    = (struct LoadingScreen*)screen;
+	const char* state = (const char*)Gen_CurrentState;
+	if (state == s->lastState) return;
+	s->lastState = state;
+
+	s->messageStr.length = 0;
+	String_AppendConst(&s->messageStr, state);
+	LoadingScreen_SetMessage(s);
+}
+
+static void GeneratingScreen_Render(void* screen, float delta) {
+	struct LoadingScreen* s = (struct LoadingScreen*)screen;
+	s->progress = Gen_CurrentProgress;
+	LoadingScreen_Render(s, delta);
+	if (Gen_IsDone()) GeneratingScreen_EndGeneration();
+}
+
+static const struct ScreenVTABLE GeneratingScreen_VTABLE = {
+	GeneratingScreen_Init,   GeneratingScreen_Update, GeneratingScreen_Free,
+	GeneratingScreen_Render, LoadingScreen_BuildMesh,
+	Screen_TInput,           Screen_InputUp,    Screen_TKeyPress,   Screen_TText,
+	Screen_TPointer,         Screen_PointerUp,  Screen_FPointer,    Screen_TMouseScroll,
+	LoadingScreen_Layout, LoadingScreen_ContextLost, LoadingScreen_ContextRecreated
+};
+void GeneratingScreen_Show(void) {
+	static const cc_string title   = String_FromConst("Generating level");
+	static const cc_string message = String_FromConst("Generating..");
+
+	LoadingScreen.VTABLE = &GeneratingScreen_VTABLE;
+	LoadingScreen_ShowCommon(&title, &message);
+}
+
+
+/*########################################################################################################################*
+*----------------------------------------------------DisconnectScreen-----------------------------------------------------*
+*#########################################################################################################################*/
+static struct DisconnectScreen {
+	Screen_Body
+	double initTime;
+	cc_bool canReconnect, lastActive;
+	int lastSecsLeft;
+	struct ButtonWidget reconnect, quit;
+
+	struct FontDesc titleFont, messageFont;
+	struct TextWidget title, message;
+	char _titleBuffer[STRING_SIZE * 2];
+	char _messageBuffer[STRING_SIZE];
+	cc_string titleStr, messageStr;
+} DisconnectScreen;
+
+static struct Widget* disconnect_widgets[4];
+#define DISCONNECT_DELAY_SECS 5
+
+static void DisconnectScreen_Layout(void* screen) {
+	struct DisconnectScreen* s = (struct DisconnectScreen*)screen;
+	Widget_SetLocation(&s->title,     ANCHOR_CENTRE, ANCHOR_CENTRE, 0, -30);
+	Widget_SetLocation(&s->message,   ANCHOR_CENTRE, ANCHOR_CENTRE, 0,  10);
+	Widget_SetLocation(&s->reconnect, ANCHOR_CENTRE, ANCHOR_CENTRE, 0,  80);
+	Widget_SetLocation(&s->quit,      ANCHOR_CENTRE, ANCHOR_CENTRE, 0, 130);
+}
+
+static void DisconnectScreen_UpdateReconnect(struct DisconnectScreen* s) {
+	cc_string msg; char msgBuffer[STRING_SIZE];
+	int elapsed, secsLeft;
+	String_InitArray(msg, msgBuffer);
+
+	if (s->canReconnect) {
+		elapsed  = (int)(Game.Time - s->initTime);
+		secsLeft = DISCONNECT_DELAY_SECS - elapsed;
+
+		if (secsLeft > 0) {
+			String_Format1(&msg, "Reconnect in %i", &secsLeft);
+		}
+		Widget_SetDisabled(&s->reconnect, secsLeft > 0);
+	}
+
+	if (!msg.length) String_AppendConst(&msg, "Reconnect");
+	ButtonWidget_Set(&s->reconnect, &msg, &s->titleFont);
+}
+
+static void DisconnectScreen_ContextLost(void* screen) {
+	struct DisconnectScreen* s = (struct DisconnectScreen*)screen;
+	Font_Free(&s->titleFont);
+	Font_Free(&s->messageFont);
+	Screen_ContextLost(screen);
+}
+
+static void DisconnectScreen_ContextRecreated(void* screen) {
+	struct DisconnectScreen* s = (struct DisconnectScreen*)screen;
+	Screen_UpdateVb(screen);
+
+	Gui_MakeTitleFont(&s->titleFont);
+	Gui_MakeBodyFont(&s->messageFont);
+	TextWidget_Set(&s->title,   &s->titleStr,   &s->titleFont);
+	TextWidget_Set(&s->message, &s->messageStr, &s->messageFont);
+
+	DisconnectScreen_UpdateReconnect(s);
+	ButtonWidget_SetConst(&s->quit, "Quit game", &s->titleFont);
+}
+
+static void DisconnectScreen_OnReconnect(void* s, void* w) {
+	Gui_Remove((struct Screen*)s);
+	Gui_ShowDefault();
+	Server.BeginConnect();
+}
+static void DisconnectScreen_OnQuit(void* s, void* w) { Window_RequestClose(); }
+
+static void DisconnectScreen_Init(void* screen) {
+	struct DisconnectScreen* s = (struct DisconnectScreen*)screen;
+	s->widgets      = disconnect_widgets;
+	s->numWidgets   = 0;
+	s->maxWidgets   = Array_Elems(disconnect_widgets);
+
+	TextWidget_Add(s, &s->title);
+	TextWidget_Add(s, &s->message);
+
+	ButtonWidget_Add(s, &s->reconnect, 300, DisconnectScreen_OnReconnect);
+	ButtonWidget_Add(s, &s->quit,      300, DisconnectScreen_OnQuit);
+	if (!s->canReconnect) s->reconnect.flags = WIDGET_FLAG_DISABLED;
+
+	/* NOTE: changing VSync can't be done within frame, causes crash on some GPUs */
+	Gfx_SetFpsLimit(Game_FpsLimit == FPS_LIMIT_VSYNC, 1000 / 5.0f);
+
+	s->initTime     = Game.Time;
+	s->lastSecsLeft = DISCONNECT_DELAY_SECS;
+	s->maxVertices  = Screen_CalcDefaultMaxVertices(s);
+}
+
+static void DisconnectScreen_Update(void* screen, float delta) {
+	struct DisconnectScreen* s = (struct DisconnectScreen*)screen;
+	int elapsed, secsLeft;
+
+	if (!s->canReconnect) return;
+	elapsed  = (int)(Game.Time - s->initTime);
+	secsLeft = DISCONNECT_DELAY_SECS - elapsed;
+
+	if (secsLeft < 0) secsLeft = 0;
+	if (s->lastSecsLeft == secsLeft && s->reconnect.active == s->lastActive) return;
+	DisconnectScreen_UpdateReconnect(s);
+
+	s->lastSecsLeft = secsLeft;
+	s->lastActive   = s->reconnect.active;
+	s->dirty        = true;
+}
+
+static void DisconnectScreen_Render(void* screen, float delta) {
+	PackedCol top    = PackedCol_Make(64, 32, 32, 255);
+	PackedCol bottom = PackedCol_Make(80, 16, 16, 255);
+	Gfx_Draw2DGradient(0, 0, Window_UI.Width, Window_UI.Height, top, bottom);
+
+	Screen_Render2Widgets(screen, delta);
+}
+
+static void DisconnectScreen_Free(void* screen) { Game_SetFpsLimit(Game_FpsLimit); }
+
+static const struct ScreenVTABLE DisconnectScreen_VTABLE = {
+	DisconnectScreen_Init,   DisconnectScreen_Update, DisconnectScreen_Free,
+	DisconnectScreen_Render, Screen_BuildMesh,
+	Menu_InputDown,          Screen_InputUp,          Screen_TKeyPress, Screen_TText,
+	Menu_PointerDown,        Screen_PointerUp,        Menu_PointerMove, Screen_TMouseScroll,
+	DisconnectScreen_Layout, DisconnectScreen_ContextLost, DisconnectScreen_ContextRecreated
+};
+void DisconnectScreen_Show(const cc_string* title, const cc_string* message) {
+	static const cc_string kick = String_FromConst("Kicked ");
+	static const cc_string ban  = String_FromConst("Banned ");
+	cc_string why; char whyBuffer[STRING_SIZE];
+	struct DisconnectScreen* s = &DisconnectScreen;
+	int i;
+
+	s->grabsInput  = true;
+	s->blocksWorld = true;
+
+	String_InitArray(s->titleStr,   s->_titleBuffer);
+	String_AppendString(&s->titleStr,   title);
+	String_InitArray(s->messageStr, s->_messageBuffer);
+	String_AppendString(&s->messageStr, message);
+
+	String_InitArray(why, whyBuffer);
+	String_AppendColorless(&why, message);
+	
+	s->canReconnect = !(String_CaselessStarts(&why, &kick) || String_CaselessStarts(&why, &ban));
+	s->VTABLE       = &DisconnectScreen_VTABLE;
+
+	Gui_Add((struct Screen*)s, GUI_PRIORITY_DISCONNECT);
+	/* Remove other screens instead of just drawing over them to reduce GPU usage */
+	for (i = Gui.ScreensCount - 1; i >= 0; i--) 
+	{
+		if (Gui_Screens[i] == (struct Screen*)s) continue;
+		Gui_Remove(Gui_Screens[i]);
+	}
+}