diff options
author | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
---|---|---|
committer | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
commit | abef6da56913f1c55528103e60a50451a39628b1 (patch) | |
tree | b3c8092471ecbb73e568cd0d336efa0e7871ee8d /src/Screens.c |
initial commit
Diffstat (limited to 'src/Screens.c')
-rw-r--r-- | src/Screens.c | 2199 |
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]); + } +} |